本项目采用C/S架构模型,主要运用的技术如下,采用sqlite3数据库来存储信息,TCP协议传输数据,通过多线程方式的处理客户端的需求,调用系统提供的各种函数接口来实现的。
优化的地方:频繁的创建和销毁线程比较浪费资源,可以利用线程池来解决。利用IO的多路复用和reactor模型来提高。
注意:在Linux下运行需要加库文件和数据库。
创建文件,client.h,client.c;server.h,server.c
编译:gcc client.c -pthread -lsqlite3
gcc server.c -pthread -lsqlite3
运行:./a.out
需要修改的地方,一个是client.c,server.h
基于TCP/UDP协议的聊天室
登录界面如下:
主要功能如下:
功能我就不一一展示了,这个我测试过了,没问题的。想测试的自己去测试下就知道了。
客户端
客户端头文件代码如下:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <time.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <pthread.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#define PORT 9999
#define SIZE 20
struct Usr
{
char name[SIZE]; //用户名
int socket;
int flag;//用来判断该用户是否被禁言,没有被禁设为0,被禁设为1
};
struct Msg
{
struct Usr usr[SIZE]; //用户数
char msg[1024]; //记录消息
char buf[1024]; //
char name[SIZE]; //帐号
char fromname[SIZE];//发送者
char toname[SIZE]; //接受者
char password[SIZE];//密码
int cmd; //权限的标志位
int filesize; //文件大小
int flag; //用来判断用户权限 0代表普通用户,1代表超级用户
};
//动态显示进度条
int show_login();
//动态显示进度条
int show_connect();
//信号注册函数
void sigfun(int signo);
//显示空行
void display_line();
//显示主界面
void maininterface();
//显示普通用户界面
void cominterface(struct Msg *msg);
//显示超级用户界面
void supinterface(struct Msg *msg);
//查看在线用户
void see_online(int socketfd, struct Msg *msg);
//保存一条聊天记录
void insert_mysql(struct Msg *msg);
//群聊
void chat_group(int socketfd, struct Msg *msg);
//发送悄悄话
void chat_private(int socketfd, struct Msg *msg);
//删除聊天记录
void del_personsql();
//查看聊天记录
void see_record();
//获取文件大小
int file_size(char* filename) ;
//上传文件
void send_file(int socketfd, struct Msg *msg);
//注销当前用户
void logout(int socketfd, struct Msg *msg);
//设置禁言
void forbid_speak(int socketfd, struct Msg *msg);
//解除禁言
void relieve_speak(int socketfd, struct Msg *msg);
//踢出聊天室
void kickout_room(int socketfd, struct Msg *msg);
//下线
void off_line(int socketfd, struct Msg *msg);
//超级用户
void superusr(int socketfd, struct Msg *msg);
//下载文件
void download_file(int socketfd, struct Msg *msg);
void fun(int socketfd, struct Msg *msg);
//普通用户
void commonusr(int socketfd, struct Msg *msg);
//创建聊天记录数据库
void set_mysql();
//注册函数
void reg(int socketfd);
//专门用来读取服务器
void * readMsg(void *v);
//登陆函数
void login(int socketfd);
// 客户端向服务器发送数据
void ask_server(int socketfd);
客户端实现代码如下:
#include "client_head.h"
int flag = 0;
int offline = 0; //用来判断该用户有没有被踢 0代表没有,1代表被踢
struct Msg tmp;
//显示空行
void display_line()
{
system("clear");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
}
//显示主界面
void maininterface()
{
display_line();
time_t tm = time(NULL);
char tstr[30] = "";
strcpy(tstr,ctime(&tm));
printf("\t\t%s\n",tstr);
printf("\t\t(~ ̄▽ ̄)→))* ̄▽ ̄*)o\n");
printf("\t\twelcome!\n");
printf("\t\t1、 注册\n");
printf("\n");
printf("\t\t2、 登陆\n");
printf("\n");
printf("\t\t3、 退出\n");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
printf("\n");
printf("\t\t请输入指令:");
}
//显示普通用户界面
void cominterface(struct Msg *msg)
{
display_line();
time_t tm = time(NULL);
char tstr[30] = "";
strcpy(tstr,ctime(&tm));
printf("\t\t%s\n",tstr);
printf("\t\t~( ̄▽ ̄~)~ welcome! %s:\n", msg->name);
printf("\n");
printf("\t\t1、 查看在线用户\n");
printf("\n");
printf("\t\t2、 群发消息\n");
printf("\n");
printf("\t\t3、 发送悄悄话\n");
printf("\n");
printf("\t\t4、 删除聊天记录\n");
printf("\n");
printf("\t\t5、 查看聊天记录\n");
printf("\n");
printf("\t\t6、 上传文件\n");
printf("\n");
printf("\t\t7、 注销当前用户\n");
printf("\n");
printf("\t\t8、 退出当前账号\n");
printf("\n");
printf("\t\t9、 下载文件\n");
printf("\n");
printf("\t\t请输入指令:");
}
//显示超级用户界面
void supinterface(struct Msg *msg)
{
display_line();
time_t tm = time(NULL);
char tstr[30] = "";
strcpy(tstr,ctime(&tm));
printf("\t\t%s\n",tstr);
printf("\t\t(((o(*゚▽゚*)o))) welcome! %s:\n", msg->name);
printf("\n");
printf("\t\t1、 查看在线用户\n");
printf("\n");
printf("\t\t2、 群发消息\n");
printf("\n");
printf("\t\t3、 发送悄悄话\n");
printf("\n");
printf("\t\t4、 删除聊天记录\n");
printf("\n");
printf("\t\t5、 查看聊天记录\n");
printf("\n");
printf("\t\t6、 设置禁言\n");
printf("\n");
printf("\t\t7、 解除禁言\n");
printf("\n");
printf("\t\t8、 退出当前账号\n");
printf("\n");
printf("\t\t9、 踢出聊天室\n");
printf("\n");
printf("\t\t请输入指令:");
}
//查看在线用户
void see_online(int socketfd, struct Msg *msg)
{
msg->cmd = 1;
write(socketfd, msg, sizeof(struct Msg));
//struct Usr usr[SIZE];
//read(socketfd, usr, sizeof(usr));
return;
}
//保存一条聊天记录
void insert_mysql(struct Msg *msg)
{
sqlite3 * database;
int ret = sqlite3_open("person.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return ;
}
//获取系统当前时间
time_t timep;
char s[30];
time(&timep);
strcpy(s,ctime(&timep));
int count = strlen(s);
s[count-1] = '\0';
char *errmsg = NULL;
char *sql = "create table if not exists person(time TEXT,fromname TEXT,toname TEXT,word TEXT)";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("用户表创建失败:%s\n", errmsg);
return;
}
char buf[100];
sprintf(buf, "insert into person values('%s','%s','%s','%s')",s,msg->fromname, msg->toname,msg->msg);
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("添加聊天记录失败:%s\n", errmsg);
return ;
}
sqlite3_close(database);
return ;
}
//群聊
void chat_group(int socketfd, struct Msg *msg)
{
msg->cmd = 2;
printf ("\t\t请输入要发送的内容: ");
//scanf("%s",msg->msg);
//while(getchar() != '\n');
fgets(msg->msg, 1024, stdin);
int len = strlen(msg->msg);
msg->msg[len-1] = '\0';
strcpy(msg->fromname, msg->name);
strcpy(msg->toname, "all");
insert_mysql(msg);
write (socketfd, msg, sizeof(struct Msg));
//printf("aaa\n");
}
//发送悄悄话
void chat_private(int socketfd, struct Msg *msg)
{
msg->cmd = 3;
printf ("\t\t请输入要发送的对象名称: ");
fgets(msg->toname, SIZE, stdin);
int len = strlen(msg->toname);
msg->toname[len-1] = '\0';
printf ("\t\t请输入要发送的内容: ");
fgets(msg->msg, 1024, stdin);
len = strlen(msg->msg);
msg->msg[len-1] = '\0';
strcpy(msg->fromname, msg->name);
insert_mysql(msg);
write(socketfd, msg, sizeof(struct Msg));
}
//删除聊天记录
void del_personsql()
{
sqlite3 * database;
int ret = sqlite3_open("person.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return;
}
char *errmsg = NULL;
char *sql = "create table if not exists person(time TEXT,fromname TEXT,toname TEXT,word TEXT)";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("用户表创建失败:%s\n", errmsg);
return;
}
char *buf = "drop table person";
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("删除聊天记录失败:%s\n", errmsg);
return;
}
sqlite3_close(database);
display_line();
printf("\t\t删除成功\n");
printf("\t\t按ENTER键返回\n");
while(getchar() != '\n');
return;
}
//查看聊天记录
void see_record()
{
sqlite3 * database;
int ret = sqlite3_open("person.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return ;
}
char *errmsg = NULL;
char *sql = "create table if not exists person(time TEXT,fromname TEXT,toname TEXT,word TEXT)";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("用户表创建失败:%s\n", errmsg);
return;
}
char **resultp = NULL;
int nrow, ncolumn;
char *buf = "select * from person";
ret = sqlite3_get_table(database, buf, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("数据库操作失败:%s\n", errmsg);
return;
}
//判断表是否为空
if(nrow == 0)
{
display_line();
printf("\t\t没有聊天记录\n");
}
else
{
int i;
system("clear");
printf ("nrow = %d, ncolumn = %d\n", nrow, ncolumn);
printf("\t\t%s",resultp[0]);
printf("\t%12s",resultp[1]);
printf("\t%8s",resultp[2]);
printf("\t%s",resultp[3]);
for (i = 4; i < (nrow+1)*ncolumn; i++)
{
if (i % ncolumn == 0)
printf ("\n");
printf ("%12s", resultp[i]);
}
printf ("\n");
}
printf("\t\t按ENTER键返回\n");
while(getchar() != '\n');
sqlite3_free_table(resultp);
sqlite3_close(database);
return;
}
//获取文件大小
int file_size(char* filename)
{
struct stat statbuf;
stat(filename,&statbuf); //记录文件大小
int size=statbuf.st_size;
return size;
}
//上传文件
void send_file(int socketfd, struct Msg *msg)
{
msg->cmd = 6;
strcpy(msg->fromname, msg->name);
printf("\t\t请输入要发送的文件名:");
fgets(msg->msg, 1024, stdin);
int len = strlen(msg->msg);
msg->msg[len-1] = '\0';
int size = file_size(msg->msg);
msg->filesize = size;
write(socketfd, msg, sizeof(struct Msg));
printf("\t\t正在上传,请稍后...\n");
sleep(1);
int fd = open(msg->msg, O_RDONLY, 0777);
if(fd == -1)
{
perror("open");
printf("文件传输失败\n");
}
char buf[65535];
memset(buf, 0, 65535);
int ret = read(fd, buf, size);
if(ret == -1)
{
perror("read");
return;
}
write(socketfd, buf, ret);
close(fd);
printf("\t\t上传文件成功\n");
sleep(1);
}
//注销当前用户
void logout(int socketfd, struct Msg *msg)
{
msg->cmd = 7;
write(socketfd, msg, sizeof(struct Msg));
display_line();
printf("\t\t正在注销...\n");
}
//设置禁言
void forbid_speak(int socketfd, struct Msg *msg)
{
msg->cmd = 6;
printf("\t\t请输入要禁言的对象: ");
fgets(msg->msg, SIZE, stdin);
int len = strlen(msg->msg);
msg->msg[len-1] = '\0';
write(socketfd, msg, sizeof(struct Msg));
}
//解除禁言
void relieve_speak(int socketfd, struct Msg *msg)
{
msg->cmd = 7;
printf("\t\t请输入要解除禁言的对象: ");
fgets(msg->msg, SIZE, stdin);
int len = strlen(msg->msg);
msg->msg[len-1] = '\0';
write(socketfd, msg, sizeof(struct Msg));
}
//踢出聊天室
void kickout_room(int socketfd, struct Msg *msg)
{
msg->cmd = 9;
printf("\t\t请输入要踢除的对象: ");
fgets(msg->msg, SIZE, stdin);
int len = strlen(msg->msg);
msg->msg[len-1] = '\0';
write(socketfd, msg, sizeof(struct Msg));
}
//下线
void off_line(int socketfd, struct Msg *msg)
{
msg->cmd = 8;
time_t tm = time(NULL);
char tstr[30] = "";
strcpy(tstr,ctime(&tm));
printf("%s用户%s下线\n",tstr,msg->name);
write(socketfd, msg, sizeof(struct Msg));
display_line();
printf("\t\t正在退出...\n");
}
//超级用户
void superusr(int socketfd, struct Msg *msg)
{
char ch[SIZE];
int x;
while (1)
{
supinterface(msg);
scanf("%s",ch);
while(getchar() != '\n');
switch(ch[0])
{
case '1': // 查看在线用户
see_online(socketfd, msg);
break;
case '2': // 群发消息
chat_group(socketfd, msg);
break;
case '3': // 发送悄悄话
chat_private(socketfd, msg);
break;
case '4': // 删除聊天记录
del_personsql();
break;
case '5': // 查看聊天记录
see_record();
break;
case '6': // 设置禁言
forbid_speak(socketfd, msg);
break;
case '7': // 解除禁言
relieve_speak(socketfd, msg);
break;
case '8': // 退出当前账号
off_line(socketfd, msg);
return;
case '9': // 踢出聊天室
kickout_room(socketfd, msg);
break;
default:
//display_line();
printf("错误指令,请重新输入\n");
sleep(1);
break;
}
}
}
//下载文件
void download_file(int socketfd, struct Msg *msg)
{
msg->cmd = 9;
printf("\t\t请输入要下载的文件名:");
fgets(msg->msg, 1024, stdin);
int len = strlen(msg->msg);
msg->msg[len-1] = '\0';
write(socketfd, msg, sizeof(struct Msg));
printf("\t\t文件内容:%s\n",msg->msg);
}
//文件下载
void fun(int socketfd, struct Msg *msg)
{
printf("\n\t\t正在下载,请稍后...\n");
int fd = open(msg->msg, O_RDWR | O_CREAT, 0777);
if(fd == -1)
{
perror("open");
printf("文件下载失败了\n");
}
int size = msg->filesize;
char buf[65535];
memset(buf, 0, 65535);
int ret = read(socketfd, buf, size);
if(ret == -1)
{
perror("read");
return;
}
write(fd, buf, ret);
sleep(1);
printf("\n\t\t文件下载完成\n");
printf("文件内容:%s\n",buf);
close(fd);
}
//普通用户
void commonusr(int socketfd, struct Msg *msg)
{
char ch[SIZE];
int x;
while (1)
{
cominterface(msg);
//scanf("%s",ch);
//while(getchar() != '\n');
fgets(ch, SIZE, stdin);
if(offline == 1)
{
display_line();
printf("\n\t\t请稍后...\n");
off_line(socketfd, msg);
return;
}
else
{
switch(ch[0])
{
case '1': // 查看在线用户
see_online(socketfd, msg);
break;
case '2': // 群发消息
chat_group(socketfd, msg);
break;
case '3': // 发送悄悄话
chat_private(socketfd, msg);
break;
case '4': // 删除聊天记录
del_personsql();
break;
case '5': // 查看聊天记录
see_record();
break;
case '6': // 上传文件
send_file(socketfd, msg);
break;
case '7': // 注销当前用户
logout(socketfd, msg);
return;
case '8': // 退出当前账号
off_line(socketfd, msg);
return;
case '9':
download_file(socketfd, msg); // 下载文件
break;
default:
//display_line();
printf("错误指令,请重新输入\n");
sleep(1);
break;
}
}
}
}
//创建聊天记录数据库
void set_mysql()
{
sqlite3 * database;
int ret = sqlite3_open("person.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return;
}
char *errmsg = NULL;
char *sql = "create table if not exists person(time TEXT,fromname TEXT,toname TEXT,word TEXT)";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("聊天记录表创建失败:%s\n", errmsg);
return;
}
sqlite3_close(database);
return;
}
//注册函数
void reg(int socketfd)
{
struct Msg msg;
memset(&msg, 0, sizeof(msg));
msg.cmd = 1;
display_line();
printf("\t\t请输入姓名:");
scanf("%s",msg.name);
while(getchar() != '\n');
printf("\n");
printf("\t\t请输入密码:");
scanf("%s",msg.password);
while(getchar() != '\n');
write(socketfd, &msg, sizeof(msg));
read(socketfd, &msg, sizeof(msg));
if(msg.cmd == 0)
{
display_line();
printf("\t\t注册失败,该用户已被注册\n");
}
if(msg.cmd == 1)
{
display_line();
set_mysql(); //注册成功就建立一个用户自己的数据库存放在本地,存放聊天记录
printf("\t\t注册成功\n");
}
sleep(2);
return;
}
//线程函数,专门用来读取服务器
void * readMsg(void *v)
{
int socketfd = *((int *)v);
struct Msg msg;
while (1)
{
int i;
time_t tm = time(NULL);
char str[30] = "";
strcpy(str,ctime(&tm));
int ret = read (socketfd, &msg, sizeof(msg));
switch (msg.cmd)
{
case 1: //显示在线用户
printf("\n\t\t当前在线户:\n");
printf("\t\t%s",str);
printf("\t\t");
for(i=0; i<SIZE; i++)
{
printf("%-5s ",msg.usr[i].name);
}
printf("\n");
break;
case 2: // 接受一条群聊信息
printf("\n\t\t%s",str);
printf ("\t\t%s给大家发了一条消息: %s\n", msg.name, msg.msg);
insert_mysql(&msg); //保存聊天信息
break;
case 3: // 接受一条私聊信息
printf("\n\t\t%s",str);
printf ("\t\t%s给你发一条消息:%s\n", msg.fromname, msg.msg);
insert_mysql(&msg);
break;
case 6: // 收到有人上传文件的消息
printf("\n\t\t%s",str);
printf("\t\t用户%s上传了一份文件%s\n", msg.fromname, msg.msg);
break;
case 7:
return NULL;
case 8:
return NULL;
case 9:
fun(socketfd, &msg);
break;
case 1003:
//display_line();
printf("\n\t\t%s",str);
printf("\t\t您已被管理员禁言\n");
//sleep(1);
break;
case 1004:
printf("\n\t\t%s",str);
printf("\t\t您已被管理员解除禁言\n");
break;
case 1005:
printf("\n\t\t%s",str);
printf("\t\t用户%s已被管理员踢出聊天室\n",msg.msg);
break;
case 1006:
display_line();
printf("\n\t\t%s",str);
printf("\t\t您已被管理员踢出聊天室\n");
printf("\t\t按ENTER键返回主菜单\n");
offline = 1;
//off_line(socketfd, msg);
break;
default:
//display_line();s
printf("错误指令,请重新输入\n");
sleep(1);
break;
}
}
}
//登陆函数
void login(int socketfd)
{
struct Msg msg;
memset(&msg, 0, sizeof(msg));
msg.cmd = 2;
display_line();
printf("\t\t请输入姓名:");
scanf("%s",msg.name);
while(getchar() != '\n');
printf("\n");
printf("\t\t请输入密码:");
scanf("%s",msg.password);
while(getchar() != '\n');
write(socketfd, &msg, sizeof(msg));
read(socketfd, &msg, sizeof(msg));
printf("%d\n",msg.cmd);
if(msg.cmd == 0)
{
display_line();
// printf("\t\t正在登陆...\n");
show_login();
display_line();
// printf("\t\t登陆成功\n");
//启动一个线程专门用来接受聊天信息
pthread_t id;
pthread_create(&id, NULL, readMsg, &socketfd);
pthread_detach(id);
offline = 0;
if(msg.flag == 1) //超级用户
superusr(socketfd, &msg);
if(msg.flag == 0) //普通用户
commonusr(socketfd, &msg);
}
if(msg.cmd == 1)
{
display_line();
printf("\t\t正在登陆...\n");
sleep(2);
display_line();
printf("\t\t密码错误\n");
}
if(msg.cmd == 2)
{
display_line();
printf("\t\t正在登陆...\n");
sleep(2);
display_line();
printf("\t\t用户不存在,请先注册\n");
}
if(msg.cmd == 3)
{
display_line();
printf("\t\t正在登陆...\n");
sleep(2);
display_line();
printf("\t\t该用户已经登陆,请勿重复登陆\n");
}
sleep(1);
return;
}
// 客户端向服务器发送数据
void ask_server(int socketfd)
{
int x;
while (1)
{
maininterface();
scanf("%d",&x);
while(getchar() != '\n');
switch(x)
{
case 1: // 注册
reg(socketfd);
break;
case 2: // 登陆
login(socketfd);
break;
case 3: // 退出
system("clear");
return;
case 4:
signal(SIGINT,sigfun);
signal(SIGQUIT,sigfun);
break;
default:
//display_line();
printf("\t\t错误指令,请重新输入\n");
sleep(1);
break;
}
}
}
//信号注册函数
void sigfun(int signo)
{
if(signo == SIGINT)
{
printf("正在退出...\n");
sleep(2);
//printf("this signal is ctrl c \n");
exit(0);
}
if(signo == SIGQUIT)
{
printf("客户端正在退出\n");
sleep(2);
//printf("this signal is ctrl '\\' \n");
return ;
}
}
//动态显示进度条
int show_connect()
{
printf("正在连接服务器,请稍候...\n");
int i, num = 1;
const char* pic = "|/-\\"; //简单动画特效
while(1)
{
if(101 == num) //当num自增到101时,进度条已满。显示Loading成功信息,并跳出死循环。
{
//printf("\n连接服务器成功\n");
//sleep(1);
break;
}
printf("[");
for(i = 0; i < num/10; i++) //当进度前进10个点时,刷新一次进度条。
printf("*"); //以'*'充当进度条。
printf("]");
printf("%d%%...%c\r", num++, pic[num%4]); //'\r'为回车符。
fflush(stdout); //清空标准输出缓冲区中多余的数据。
usleep(40000); //这里通过修改睡眠时间来控制进度条更新速度。
}
return 0;
}
//动态显示进度条
int show_login()
{
printf("正在登陆,请稍候...\n");
int i, num = 1;
const char* pic = "|/-\\"; //简单动画特效
while(1)
{
if(101 == num) //当num自增到101时,进度条已满。显示Loading成功信息,并跳出死循环。
{
printf("\n帐号登陆成功\n");
sleep(1);
break;
}
printf("[");
for(i = 0; i < num/10; i++) //当进度前进10个点时,刷新一次进度条。
printf("*"); //以'*'充当进度条。
printf("]");
printf("%d%%...%c\r", num++, pic[num%4]); //'\r'为回车符。
fflush(stdout); //清空标准输出缓冲区中多余的数据。
usleep(40000); //这里通过修改睡眠时间来控制进度条更新速度。
}
return 0;
}
int main()
{
show_connect();
// 创建与服务器通信的套接字
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("10.31.10.165",&(addr.sin_addr));
// 成功的情况下,可以通过socketfd与服务器进行通信
int ret = connect(socketfd, (struct sockaddr *)&addr, sizeof(addr));
if (ret == -1)
{
perror ("connect");
return -1;
}
printf("\n连接服务器成功\n");
sleep(1);
signal(SIGINT,sigfun);
signal(SIGQUIT,sigfun);
//printf ("成功连上服务器\n");
ask_server(socketfd);
close(socketfd);
return 0;
}
服务器端
服务器端头文件代码如下:
#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 <sqlite3.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#define SIZE 20
#define ADDR "10.31.30.69" //根据自己电脑的ip地址写
#define PORT 9999
#define TRUE 1
#define FALSE -1
struct Usr
{
char name[SIZE]; //用户名
int socket;
int flag;//用来判断该用户是否被禁言,没有被禁设为0,被禁设为1
};
struct Msg
{
struct Usr usr[SIZE]; //用户数
char msg[1024]; //记录消息
char buf[1024]; //
char name[SIZE]; //帐号
char fromname[SIZE];//发送者
char toname[SIZE]; //接受者
char password[SIZE];//密码
int cmd; //权限的标志位
int filesize; //文件大小
int flag; //用来判断用户权限 0代表普通用户,1代表超级用户
};
//信号注册函数
void sigfun(int signo);
//查看在线用户
void see_online(int client_socket, struct Msg *msg);
//保存一条聊天记录
void insert_record(struct Msg *msg);
//群聊
void chat_group(int client_socket, struct Msg *msg);
//私聊
void chat_private(int client_socket, struct Msg *msg);
//获取文件大小
int file_size(char* filename) ;
//上传文件
void send_file(int client_socket, struct Msg *msg);
//从用户数据库删除一个用户
void del_fromsql(struct Msg *msg);
//注销用户
void logout(int client_socket,struct Msg *msg);
//用户下线
void off_line(int client_socket,struct Msg *msg);
//用户下载文件
void download_file(int client_socket,struct Msg *msg);
//设置禁言
void forbid_speak(int client_socket,struct Msg *msg);
//解除禁言
void relieve_speak(int client_socket,struct Msg *msg);
//踢出聊天室
void kickout_room(int client_socket,struct Msg *msg);
//超级用户
void surperusr(int client_socket);
//普通用户
void commonusr(int client_socket);
//添加到在线用户列表
void add_usr(struct Msg *msg, int client_socket);
// 注册
void reg(int client_socket, struct Msg *msg);
// 登陆
void login(int client_socket, struct Msg *msg);
//查看用户权限
int check_root(struct Msg *msg);
//线程函数
void* hanld_client(void* v);
//检查该用户是否在线
int check_ifonline(struct Msg *msg);
//查找用户名
int find_name(struct Msg *msg);
//查找用户名和密码
int find_np(struct Msg *msg);
//添加用户到数据库
int insert_sql(struct Msg *msg);
//建立所有用户的聊天记录数据库
void setup_record();
//建立用户数据库,并在里面添加超级用户
int setup_sql();
// 初始化套接字,返回监听套接字
int init_socket();
// 处理客户端连接,返回与连接上的客户端通信的套接字
int MyAccept(int listen_socket);
//信号注册函数
void sigfun(int signo);
//用户上线提醒
void user_login();
//用户下线提醒
void offline();
服务器端实现代码如下:
#include "server_head.h"
struct Usr usr[SIZE];
int count; //记录在线人数
pthread_mutex_t mutex; //申请互斥锁,用来避免两个客户端同时访问全局变量
//查看在线用户
void see_online(int client_socket, struct Msg *msg)
{
int i;
for(i=0; i<20; i++)
{
msg->usr[i] = usr[i];
}
write(client_socket, msg, sizeof(struct Msg));
}
//保存一条聊天记录
void insert_record(struct Msg *msg)
{
sqlite3 * database;
int ret = sqlite3_open("allrecord.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return ;
}
//获取系统当前时间
time_t timep;
char s[30];
time(&timep);
strcpy(s,ctime(&timep));
int count = strlen(s);
s[count-1] = '\0';
char *errmsg = NULL;
char *sql = "create table if not exists allrecord(time TEXT,fromname TEXT,toname TEXT,word TEXT)";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("聊天记录表创建失败:%s\n", errmsg);
return;
}
char buf[100];
sprintf(buf, "insert into allrecord values('%s','%s','%s','%s')",s,msg->fromname, msg->toname,msg->msg);
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("添加聊天记录失败:%s\n", errmsg);
return ;
}
sqlite3_close(database);
return ;
}
//群聊
void chat_group(int client_socket, struct Msg *msg)
{
int i = 0;
for (i = 0; i < SIZE; i++)
{
if (usr[i].socket != 0 && strcmp(msg->fromname, usr[i].name) == 0)
{
break;
}
}
if(usr[i].flag == 0) //判断该用户有没有被禁言
{
time_t tm = time(NULL);
char str[30] = "";
strcpy(str,ctime(&tm));
printf("%s%s发了一条群消息\n",str,msg->fromname);
insert_record(msg);
for (i = 0; i < SIZE; i++)
{
if (usr[i].socket != 0 && strcmp(msg->fromname, usr[i].name) != 0)
{
write (usr[i].socket, msg, sizeof(struct Msg));
}
}
}
else
{
msg->cmd = 1003;
write (client_socket, msg, sizeof(struct Msg));
}
}
//私聊
void chat_private(int client_socket, struct Msg *msg)
{
int i;
for (i = 0; i < SIZE; i++)
{
if (usr[i].socket != 0 && strcmp(msg->fromname, usr[i].name) == 0)
{
break;
}
}
if(usr[i].flag == 0)
{
time_t tm = time(NULL);
char str[30] = "";
strcpy(str,ctime(&tm));
printf("%s%s给%s发了一条消息\n",str,msg->fromname,msg->toname);
insert_record(msg);
for (i = 0; i < SIZE; i++)
{
if (usr[i].socket != 0 && strcmp(usr[i].name, msg->toname)==0)
{
write (usr[i].socket, msg, sizeof(struct Msg));
break;
}
}
}
else
{
msg->cmd = 1003;
write (client_socket, msg, sizeof(struct Msg));
}
}
//获取文件大小
int file_size(char* filename)
{
struct stat statbuf;
stat(filename,&statbuf);
int size=statbuf.st_size;
return size;
}
//上传文件
void send_file(int client_socket, struct Msg *msg)
{
time_t tm = time(NULL);
char tstr[30] = "";
strcpy(tstr,ctime(&tm));
printf("%s用户%s在聊天室上传了一个文件 %s\n",tstr,msg->fromname,msg->msg);
int i;
for (i = 0; i < SIZE; i++)
{
if (usr[i].socket != 0 && strcmp(usr[i].name, msg->fromname) != 0)
{
write (usr[i].socket, msg, sizeof(struct Msg));
break;
}
}
int fd = open(msg->msg, O_RDWR | O_CREAT, 0777);
if(fd == -1)
{
perror("open");
printf("文件传输失败\n");
}
int size = msg->filesize;
char buf[65535];
memset(buf, 0, 65535);
int ret = read(client_socket, buf, size);
if(ret == -1)
{
perror("read");
return;
}
write(fd, buf, ret);
close(fd);
}
//从用户数据库删除一个用户
void del_fromsql(struct Msg *msg)
{
sqlite3 * database;
int ret = sqlite3_open("usr.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return;
}
char *errmsg = NULL;
char buf[100];
sprintf(buf, "delete from usr where name = '%s'", msg->name);
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("删除用户失败:%s\n", errmsg);
return;
}
sqlite3_close(database);
printf("用户%s已删除\n",msg->name); //可以添加询问功能
return;
}
//注销用户
void logout(int client_socket,struct Msg *msg)
{
int i,j;
for(i=0; i<count; i++)
{
if(strcmp(msg->name, usr[i].name) == 0)
break;
}
for(j=i; j<count; j++)
{
usr[j] = usr[j+1];
}
count--;
printf("正在注销用户%s\n",msg->name);
del_fromsql(msg);
printf("用户注销成功\n");
write(client_socket, msg, sizeof(struct Msg));
return;
}
//用户下线
void off_line(int client_socket,struct Msg *msg)
{
time_t tm = time(NULL);
char tstr[30] = "";
strcpy(tstr,ctime(&tm));
pthread_mutex_lock(&mutex); //上锁
printf("%s用户%s下线\n",tstr,msg->name);
insert_record(msg);
int i,j;
for(i=0; i<count; i++)
{
if(strcmp(msg->name, usr[i].name) == 0)
break;
}
for(j=i; j<count; j++)
{
usr[j] = usr[j+1];
}
count--;
pthread_mutex_unlock(&mutex); // 解锁
write(client_socket, msg, sizeof(struct Msg));
return;
}
//用户下载文件
void download_file(int client_socket,struct Msg *msg)
{
time_t tm = time(NULL);
char tstr[30] = "";
strcpy(tstr,ctime(&tm));
printf("%s用户%s在聊天室下载了一个文件: %s\n",tstr,msg->name,msg->msg);
int size = file_size(msg->msg);
msg->filesize = size;
write(client_socket, msg, sizeof(struct Msg));
sleep(1);
int fd = open(msg->msg, O_RDONLY, 0777);
if(fd == -1)
{
perror("open");
printf("文件下载失败\n");
}
char buf[65535];
memset(buf, 0, 65535);
int ret = read(fd, buf, size);
if(ret == -1)
{
perror("read");
return;
}
write(client_socket, buf, ret);
close(fd);
}
//踢出聊天室
void kickout_room(int client_socket,struct Msg *msg)
{
msg->cmd = 1005;
printf("用户%s已被踢出聊天室\n",msg->msg);
pthread_mutex_lock(&mutex);
int i;
for (i = 0; i < SIZE; i++)
{
if (usr[i].socket != 0 && strcmp(usr[i].name, msg->msg) != 0)
{
//给在线用户通知某某某被踢出聊天室
write (usr[i].socket, msg, sizeof(struct Msg));
}
}
pthread_mutex_unlock(&mutex);
for (i = 0; i < SIZE; i++)
{
if (usr[i].socket != 0 && strcmp(usr[i].name, msg->msg) == 0)
{
break;
}
}
msg->cmd = 1006;
//strcpy(msg->name,msg->msg);
//off_line(usr[i].socket,msg);
write (usr[i].socket, msg, sizeof(struct Msg));
}
//设置禁言
void forbid_speak(int client_socket,struct Msg *msg)
{
msg->cmd = 1003;
printf("用户%s已被禁言\n",msg->msg);
pthread_mutex_lock(&mutex);
int i;
for (i = 0; i < SIZE; i++)
{
if (usr[i].socket != 0 && strcmp(usr[i].name, msg->msg)==0)
{
write (usr[i].socket, msg, sizeof(struct Msg));
usr[i].flag = 1;
break;
}
}
pthread_mutex_unlock(&mutex);
}
//解除禁言
void relieve_speak(int client_socket,struct Msg *msg)
{
msg->cmd = 1004;
printf("用户%s已被解除禁言\n",msg->msg);
pthread_mutex_lock(&mutex);
int i;
for (i = 0; i < SIZE; i++)
{
if (usr[i].socket != 0 && strcmp(usr[i].name, msg->msg)==0)
{
write (usr[i].socket, msg, sizeof(struct Msg));
usr[i].flag = 0;
break;
}
}
pthread_mutex_unlock(&mutex);
}
//超级用户
void surperusr(int client_socket)
{
struct Msg msg;
while(1)
{
int ret = read(client_socket, &msg, sizeof(msg));
if (ret == -1)
{
perror ("read");
break;
}
if (ret == 0)
{
time_t tm = time(NULL);
char tstr[30] = "";
strcpy(tstr,ctime(&tm));
printf ("%s客户端退出\n",tstr);
break;
}
switch (msg.cmd)
{
case 1:
see_online(client_socket, &msg);
break;
case 2:
chat_group(client_socket, &msg);
break;
case 3:
chat_private(client_socket, &msg);
break;
case 6:
forbid_speak(client_socket, &msg); // 设置禁言
break;
case 7:
relieve_speak(client_socket,&msg); // 解除禁言
break;
case 8:
off_line(client_socket,&msg);
return;
case 9:
kickout_room(client_socket,&msg); // 踢出聊天室
break;
default:
printf("输入有误,请重新输入\n");
sleep(1);
break;
}
}
}
//普通用户
void commonusr(int client_socket)
{
struct Msg msg;
while(1)
{
int ret = read(client_socket, &msg, sizeof(msg));
if (ret == -1)
{
perror ("read");
break;
}
if (ret == 0)
{
time_t tm = time(NULL);
char tstr[30] = "";
strcpy(tstr,ctime(&tm));
printf ("%s客户端退出\n",tstr);
break;
}
switch (msg.cmd)
{
case 1:
see_online(client_socket, &msg);
break;
case 2:
chat_group(client_socket, &msg);
break;
case 3:
chat_private(client_socket, &msg);
break;
case 6:
send_file(client_socket, &msg);
break;
case 7:
logout(client_socket,&msg);
return;
case 8:
off_line(client_socket,&msg);
return;
case 9:
download_file(client_socket,&msg);
break;
default:
printf("输入有误,请重新输入\n");
sleep(1);
break;
}
}
}
//添加到在线用户列表
void add_usr(struct Msg *msg, int client_socket)
{
pthread_mutex_lock(&mutex); // 抢锁
strcpy(usr[count].name, msg->name);
usr[count].socket = client_socket;
count++;
pthread_mutex_unlock(&mutex); // 解锁
}
// 注册
void reg(int client_socket, struct Msg *msg)
{
//查找用户是否已经被注册
printf("正在查找该用户是否被注册...\n");
if(find_name(msg) == TRUE)
{
printf("用户%s已经被注册\n",msg->name);
msg->cmd = 0;
}
else
{
if(insert_sql(msg) == TRUE)
{
msg->cmd = 1;
printf("用户%s成功添加到数据库\n",msg->name);
}
}
write(client_socket, msg, sizeof(struct Msg));
}
// 登陆
void login(int client_socket, struct Msg *msg)
{
int flag1 = 0; //用来判断该用户有没有成功登陆 1代表成功
//检查该用户有没有注册
printf("正在查找该用户有没有注册...\n");
if(find_name(msg) == TRUE)
{
if(find_np(msg) == TRUE)
{
if(check_ifonline(msg) == TRUE)
{
msg->cmd = 3;
printf("用户%s已经登陆过了\n",msg->name);
}
else
{
msg->cmd = 0;
printf("检查该用户权限\n");
if(check_root(msg) == TRUE)
{
printf("该用户是超级用户\n");
msg->flag = 1;
}
else
{
printf("该用户是普通用户\n");
msg->flag = 0;
}
printf("用户%s登陆成功\n",msg->name);
flag1 = 1;
add_usr(msg, client_socket); //添加到在线用户列表
}
}
else
{
msg->cmd = 1;
printf("用户%s密码输入错误\n",msg->name);
}
}
else
{
msg->cmd = 2;
printf("用户%s还没有注册\n",msg->name);
}
write(client_socket, msg, sizeof(struct Msg));
if(flag1 == 1)
{
if(msg->flag == 1)
surperusr(client_socket);
if(msg->flag == 0)
commonusr(client_socket);
}
}
//查看用户权限
int check_root(struct Msg *msg)
{
if(strcmp(msg->name, "root") == 0)
return TRUE;
else
return FALSE;
}
//线程函数 每接受到一个客户端就创建一个新的线程
void* hanld_client(void* v)
{
struct Msg msg;
int client_socket = *((int *)v);
while(1)
{
// 从客户端读一个结构体数据
int ret = read(client_socket, &msg, sizeof(msg));
if (ret == -1)
{
perror ("read");
break;
}
// 代表客户端退出
if (ret == 0)
{
time_t tm = time(NULL);
char tstr[30] = "";
strcpy(tstr,ctime(&tm));
printf ("%s客户端退出\n",tstr);
break;
}
printf("从客户端读到一个用户:%s %s \n", msg.name, msg.password);
switch (msg.cmd)
{
case 1:
reg(client_socket, &msg);
break;
case 2:
login(client_socket, &msg);
break;
}
}
close (client_socket);
}
//检查该用户是否在线
int check_ifonline(struct Msg *msg)
{
int i;
for(i=0; i<count; i++)
{
if(strcmp(msg->name, usr[i].name) == 0)
return TRUE;
}
if(i == count)
return FALSE;
}
//查找用户名
int find_name(struct Msg *msg)
{
sqlite3 * database;
int ret = sqlite3_open("usr.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return FALSE;
}
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select name from usr";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("用户查找失败:%s\n", errmsg);
return FALSE;
}
int i;
for(i=0; i<nrow+1; i++)
{
if(strcmp(resultp[i], msg->name) == 0)
return TRUE;
}
return FALSE;
}
//查找用户名和密码
int find_np(struct Msg *msg)
{
sqlite3 * database;
int ret = sqlite3_open("usr.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return FALSE;
}
char *errmsg = NULL;
char **resultp = NULL;
int nrow, ncolumn;
char *sql = "select * from usr";
ret = sqlite3_get_table(database, sql, &resultp, &nrow, &ncolumn, &errmsg);
if (ret != SQLITE_OK)
{
printf ("用户查找失败:%s\n", errmsg);
return FALSE;
}
int i;
for(i=0; i<(nrow+1)*ncolumn; i++)
{
if(strcmp(resultp[i], msg->name) == 0 &&
strcmp(resultp[i+1], msg->password) == 0)
return TRUE;
}
return FALSE;
}
//添加用户到数据库
int insert_sql(struct Msg *msg)
{
sqlite3 * database;
int ret = sqlite3_open("usr.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return FALSE;
}
char *errmsg = NULL;
char buf[100];
sprintf(buf, "insert into usr values('%s','%s');", msg->name, msg->password);
ret = sqlite3_exec(database, buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("添加用户失败:%s\n", errmsg);
return FALSE;
}
sqlite3_close(database);
return TRUE;
}
//建立所有用户的聊天记录数据库
void setup_record()
{
sqlite3 * database;
int ret = sqlite3_open("allrecord.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return;
}
char *errmsg = NULL;
char *sql = "create table if not exists allrecord(time TEXT,fromname TEXT,toname TEXT,word TEXT)";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("聊天记录表创建失败:%s\n", errmsg);
return;
}
sqlite3_close(database);
return;
}
//建立用户数据库,并在里面添加超级用户
int setup_sql()
{
sqlite3 * database;
int ret = sqlite3_open("usr.db", &database);
if (ret != SQLITE_OK)
{
printf ("打开数据库失败\n");
return FALSE;
}
char *errmsg = NULL;
char *sql = "create table if not exists usr(name TEXT unique,password TEXT)";
ret = sqlite3_exec(database, sql, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf ("用户表创建失败:%s\n", errmsg);
return FALSE;
}
// struct Msg msg;
// strcpy(msg.name, "root");
// strcpy(msg.password, "123");
// insert_sql(&msg);
sqlite3_close(database);
return TRUE;
}
// 初始化套接字,返回监听套接字
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 = inet_addr(ADDR); // 使用本地的IP地址
int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr));
if (ret == -1)
{
perror ("bind");
return -1;
}
// 3、监听本地套接字
ret = listen(listen_socket, SIZE);
if (ret == -1)
{
perror ("listen");
return -1;
}
printf ("等待客户端连接.......\n");
return listen_socket;
}
// 处理客户端连接,返回与连接上的客户端通信的套接字
int MyAccept(int listen_socket)
{
struct sockaddr_in client_addr;
int len = sizeof(client_addr);
int client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, &len);
if (client_socket == -1)
{
perror ("accept");
return -1;
}
time_t tm = time(NULL);
char str[30] = "";
strcpy(str,ctime(&tm));
//printf ("成功接收一个客户端: %s\n", inet_ntoa(client_addr.sin_addr));
printf ("%s成功接收一个客户端\n",str);
return client_socket;
}
int main()
{
setup_sql(); //建立用户数据库
setup_record(); //建立聊天记录数据库
pthread_mutex_init(&mutex, NULL);
int listen_socket = init_socket(); //初始化监听套接字
while (1)
{
// 获取与客户端连接的套接字
int client_socket = MyAccept(listen_socket);
// 创建一个线程去处理客户端的请求,主线程依然负责监听
pthread_t id;
pthread_create(&id, NULL, hanld_client, &client_socket);
pthread_detach(id); //设置线程为分离态,由系统自动回收
}
close (listen_socket);
pthread_mutex_destroy(&mutex);
return 0;
}