前言:
最近看到大佬所写网络聊天室设计颇有感触,恰逢刚好学到这块,因此结合自己理解和自己构思写了这篇拙作,如有不当之处请见谅,菜鸟之作可以提点合理化建议。本人参考这篇文章https://blog.csdn.net/weixin_43164603/article/details/107301548https://blog.csdn.net/weixin_43164603/article/details/107301548
一.概述
网络聊天室设计内容
1.用户管理
用户注册,登录,退出,注销
2.聊天室管理
聊天室注册,登录,查看已有房间,退出,注销
3.聊天管理
聊天,退出,查看聊天历史记录
二.知识储备
在该项目中用到文件IO,标准IO,Makefile,sqlite3数据库,TCP协议,epoll并发技术及相关网络编程技术,C语言相关知识
三.设计思维
1.并发流程图
2.客户机流程图
四.系统设计
网络聊天室项目基于TCP协议的服务器和客户机实现,同时利用epoll并发技术可以实现多用户同时登陆进行相互聊天,在客户端可以进行用户注册进而转至一级界面进行用户登录和退出,在用户登陆成功后可以注册聊天室与用户注册同理,同时也可以查看已有聊天室数量,可以选择退出或者进行用户注销返回用户登录界面,用户登录聊天室则实现服务器与客户机连接进行传输数据,可以输入“quit”退出聊天界面,同样可以查看聊天记录和退出返回上级界面或注销聊天室,在这里聊天记录由服务器记录因此是所有聊天室聊天记录,大致设计如此。
1.系统数据结构设计
对于用户定义结构体初始化用户名和密码,注册则将结构体中信息插入数据库中保存,对于聊天室定义结构体初始化房间号和密码,注册则将结构体中信息插入数据库中保存
typedef struct _users{
char name[256];//用户名
int password;//密码
}user_t;//用户
typedef struct _rooms{
int room_number;//房间号
int password;//密码
}room_t;//聊天室
2.系统主要函数设计
2.1客户机
2.1.1void print_menu_one_func();
该函数功能为打印一级界面采单位用户提供选择
2.1.2void print_menu_two_func();
该函数功能为打印二级界面采单位用户提供选择
2.1.3void print_menu_three_func();
该函数功能为打印三级界面采单位用户提供选择
2.1.4int create_tables_func(sqlite3 *my_db,const char *sql);
该函数功能为在数据库中创建表格保存用户的用户名和密码
参数:my_db:数据库句柄
sql:创建表格的sql语句
2.1.5int insert_tables_user_func(sqlite3 *my_db,user_t user,const char *tablename);
该函数功能为向哪个表中插入用户信息
参数:my_db:数据库句柄
user:用户结构体
tablename:数据库中对应的表名
2.1.6int judge_user_func(sqlite3 *my_db,user_t user,const char *tablename);
该函数功能为判断该用户是否已经注册
参数:my_db:数据库句柄
user:用户结构体
tablename:数据库中对应的表名
2.1.7int delete_user_func(sqlite3 *my_db,user_t user,const char *tablename);
该函数功能为注销用户,在数据库中删除用户信息
参数:my_db:数据库句柄
user:用户结构体
tablename:数据库中对应的表名
2.1.8int register_user_func(sqlite3 *my_db,user_t *user,const char *tablename);
该函数功能为用户登录,在登录前会判断该用户是否已经注册,在登陆的同时会将登录时间及用户名写入登录日志
参数:my_db:数据库句柄
user:用户结构体(这里使用地址传参,后面查询单词,并将记录写入文件要用到用户信息)
tablename:数据库中对应的表名
2.1.9int insert_tables_room_func(sqlite3 *my_db,room_t room,const char *tablename);
该函数功能为向哪个表中插入房间信息
参数:my_db:数据库句柄
room:房间结构体
tablename:数据库中对应的表名
2.1.10int judge_room_func(sqlite3 *my_db,room_t room,const char *tablename);
该函数功能为判断该房间是否已经注册
参数:my_db:数据库句柄
room:房间结构体
tablename:数据库中对应的表名
2.1.11int register_room_func(sqlite3 *my_db,room_t *room,const char *tablename);
该函数功能为房间登录,在登录前会判断该房间是否已经注册,在登陆的同时会将登录时间及房间号写入登录日志
参数:my_db:数据库句柄
room:房间结构体
tablename:数据库中对应的表名
2.1.12int delete_room_func(sqlite3 *my_db,room_t room,const char *tablename);
该函数功能为注销房间,在数据库中删除房间信息
参数:my_db:数据库句柄
room:用户结构体
tablename:数据库中对应的表名
2.1.13int search_room_func(sqlite3 *my_db,const char *tablename);
该函数功能为查找所有已经注册了的房间
参数:my_db:数据库句柄
tablename:数据库中对应的表名
2.1.14int search_room_history_func();
该函数功能为查看所有聊天室聊天记录
2.1.15int client_user_func(int socket_fd);
该函数功能为客户机与服务器进行连接
参数:socket_fd,依据TCP协议创建的流式套接字
2.1.16int main函数
该函数主要创建客户端流式套接字,数据库句柄,打印相关帮助信息为用户提供选择,不同选择会进入不同分支
2.2服务器
2.2.1int sever_func(int socket_fd);
该函数功能为流式套接字绑定相关属性,并设为监听状态
参数:socket_fd,依据TCP协议创建的流式套接字
2.2.2int create_user_history_func(const char *msg);
该函数功能为创建聊天历史记录
参数:msg 客户机发来的数据
2.2.3int main函数
该主函数主要负责创建流式套接字,创建epoll实例,事件结构体,就绪事件数组,通过epoll并发实现与多客户机接收和发送数据
五.运行效果
1.客户机
1.1用户注册
1.2用户登录
1.3注册房间
1.4登录房间
1.5查看已有房间
1.6聊天
1.7聊天记录
1.8房间注销
1.9用户注销
1.10最终退出
2.服务器
六.总结
通过这次项目编写,和大佬对比也找到了自己的差距,知道哪一方面做的不是很好,后期可以完善,经过项目实战也让自己对知识有了更深入的了解,对TCP和epoll也更加的熟练了,当然在代码实现这块也存在很多不足,像数据收发存在不严谨问题,无法显示对方名称,也无法查看仅自己聊天室记录,功能实现起来不完善,这些都是以后需要改进的地方,总之,这一项目对我来说试一次很好的练习,也知道了自己的不足,以后会继续完善下去,我想更多的是有了自己的理解,有什么不当的请指正,谢谢!
七.代码
1.客户机
1.1client.c
#include"client.h"
//一级界面
void print_menu_one_func(){
printf("\t-----------------------------------------------------------\n");
printf("\t| 网络聊天室 (1)|\n");
printf("\t| 版本 :4.6.2 |\n");
printf("\t| 创作者 :晴耕雨读912 |\n");
printf("\t| 功能 :(用户管理) |\n");
printf("\t| [1] 登录 |\n");
printf("\t| [2] 注册 |\n");
printf("\t| [3] 退出 |\n");
printf("\t| 注意 :用户只有登陆成功后才能进入查询聊天室状态界面 |\n");
printf("\t-----------------------------------------------------------\n");
}
//二级界面
void print_menu_two_func(){
printf("\t-----------------------------------------------------------\n");
printf("\t| 网络聊天室 (2)|\n");
printf("\t| 版本 :4.6.2 |\n");
printf("\t| 创作者 :晴耕雨读912 |\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| 注意 :用户只有登陆成功后才能进入聊天室聊天界面 |\n");
printf("\t-----------------------------------------------------------\n");
}
//三级界面
void print_menu_three_func(){
printf("\t-----------------------------------------------------------\n");
printf("\t| 网络聊天室 (3)|\n");
printf("\t| 版本 :4.6.2 |\n");
printf("\t| 创作者 :晴耕雨读912 |\n");
printf("\t| 功能 :(聊天管理) |\n");
printf("\t| [1] 聊天 |\n");
printf("\t| [2] 历史记录 |\n");
printf("\t| [3] 退出 |\n");
printf("\t| [4] 注销聊天室 |\n");
printf("\t| 注意 :用户只有登陆成功后才能进入聊天室聊天界面 |\n");
printf("\t-----------------------------------------------------------\n");
}
//创建用户数据表函数
int create_tables_func(sqlite3 *my_db,const char *sql){
if(NULL==my_db || NULL==sql){
printf("入参失败,请检查...\n");
return -1;
}
char *errmage;
int ret=sqlite3_exec(my_db,sql,NULL,NULL,&errmage);
if(SQLITE_OK!=ret){
printf("创建数据表失败:%s\n",errmage);
return -2;
}
return 0;
}
//用户数据表插入数据函数
int insert_tables_user_func(sqlite3 *my_db,user_t user,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
memset(&user,0,sizeof(user));
printf("请输入用户名>>");
scanf("%s",user.name);
printf("请输入用户名密码>>");
scanf("%d",&user.password);
char *sql_user=sqlite3_mprintf("insert into %q values('%q',%d)",tablename,user.name,user.password);
char *errmage;
int ret=sqlite3_exec(my_db,sql_user,NULL,NULL,&errmage);
if(SQLITE_OK!=ret){
printf("数据表插入数据失败:%s\n",errmage);
return -2;
}
sqlite3_free(sql_user);
return 0;
}
//判断用户是否存在函数
int judge_user_func(sqlite3 *my_db,user_t user,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
int my_callback_func1(void *data,int argc,char **argv,char **zazColName){
char *name=(char *)data;
//printf("%s\n",argv[0]);
//printf("%s\n",name);
int a=strcmp(name,argv[0]);
//printf("%d\n",a);
if(strcmp(name,argv[0])){
printf("用户名不存在...\n");
return -1;
}
return 0;
}
int my_callback_func2(void *data,int argc,char **argv,char **zazColName){
int password=*((int *)data);
//printf("%s\n",argv[1]);
//printf("%d\n",password);
int value=atoi(argv[1]);
//printf("%d\n",value);
if(password!=value){
printf("密码不正确,请重新输入...\n");
return -1;
}
return 0;
}
char *errmage;
const char *sql_func1=sqlite3_mprintf("select *from %q where 用户名='%q'",tablename,user.name);
const char *sql_func2=sqlite3_mprintf("select *from %q where 用户名='%q'",tablename,user.name);
//printf("%s\n",sql_func1);
//printf("%s\n",sql_func2);
int ret=sqlite3_exec(my_db,sql_func1,&my_callback_func1,&user.name,&errmage);
if(SQLITE_OK!=ret){
printf("数据表查找用户名数据失败:%s\n",errmage);
return -2;
}
ret=sqlite3_exec(my_db,sql_func2,&my_callback_func2,&user.password,&errmage);
if(SQLITE_OK!=ret){
printf("数据表查找用户密码数据失败:%s\n",errmage);
return -2;
}
return 0;
}
//用户登录函数
int register_user_func(sqlite3 *my_db,user_t *user,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
memset(user,0,sizeof(user));
printf("请输入用户名>>");
scanf("%s",user->name);
printf("请输入用户名密码>>");
scanf("%d",&user->password);
int value=judge_user_func(my_db,*user,tablename);
int get_line(FILE *fp){
int ret=0;
int count=0;
while((ret=fgetc(fp))!=-1){
if(ret=='\n'){
count++;
}
}
return count;
}
if(0==value){
printf("登录成功,请开始您的非凡之旅...\n");
//创建日志文件,将用户登录信息写入日志
FILE *fp=fopen("user_register.txt","a+");
if(NULL==fp){
perror("fopen error");
return -1;
}
static time_t sec=0;
static time_t old_sec=0;
int line=get_line(fp);
time(&sec);
if(sec!=old_sec){
struct tm *my_time=localtime(&sec);
line++;
//拼接用户信息
fprintf(fp, "%d.%4d-%02d-%02d %02d:%02d:%02d:%s\n", line, \
my_time->tm_year+1900, my_time->tm_mon+1, my_time->tm_mday, \
my_time->tm_hour, my_time->tm_min, my_time->tm_sec,user->name);
fflush(fp);//手动刷新缓冲区
old_sec = sec;
}
}else{
return -1;
}
return 0;
}
//注销用户函数
int delete_user_func(sqlite3 *my_db,user_t user,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
memset(&user,0,sizeof(user));
printf("请输入用户名>>");
scanf("%s",user.name);
printf("请输入用户名密码>>");
scanf("%d",&user.password);
int value=judge_user_func(my_db,user,tablename);
char *sql_user=sqlite3_mprintf("delete from %q where 用户名='%q'",tablename,user.name);
char *errmage;
int ret=sqlite3_exec(my_db,sql_user,NULL,NULL,&errmage);
if(SQLITE_OK!=ret){
printf("数据表删除数据失败:%s\n",errmage);
return -2;
}
sqlite3_free(sql_user);
printf("注销用户成功...\n");
return 0;
}
//房间数据表插入数据函数
int insert_tables_room_func(sqlite3 *my_db,room_t room,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
memset(&room,0,sizeof(room));
printf("请输入房间号>>");
scanf("%d",&room.room_number);
printf("请输入房间密码>>");
scanf("%d",&room.password);
char *sql_room=sqlite3_mprintf("insert into %q values(%d,%d)",tablename,room.room_number,room.password);
char *errmage;
int ret=sqlite3_exec(my_db,sql_room,NULL,NULL,&errmage);
if(SQLITE_OK!=ret){
printf("数据表插入数据失败:%s\n",errmage);
return -2;
}
sqlite3_free(sql_room);
return 0;
}
//判断房间是否存在函数
int judge_room_func(sqlite3 *my_db,room_t room,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
int my_callback_func1(void *data,int argc,char **argv,char **zazColName){
int number=*((int *)data);
//printf("%d\n",number);
//printf("%s\n",argv[0]);
int value=atoi(argv[0]);
//printf("%d\n",value);
if(number!=value){
printf("房间不存在...\n");
return -1;
}
return 0;
}
int my_callback_func2(void *data,int argc,char **argv,char **zazColName){
int password=*((int *)data);
//printf("%s\n",argv[1]);
printf("%d\n",password);
int value=atoi(argv[1]);
//printf("%d\n",value);
if(password!=value){
printf("房间密码不正确,请重新输入...\n");
return -1;
}
return 0;
}
const char *sql_func1=sqlite3_mprintf("select *from %q where 房间号=%d",tablename,room.room_number);
const char *sql_func2=sqlite3_mprintf("select *from %q where 房间号=%d",tablename,room.room_number);
//printf("%s\n",sql_func1);
//printf("%s\n",sql_func2);
char *errmage;
int ret=sqlite3_exec(my_db,sql_func1,&my_callback_func1,&room.room_number,&errmage);
if(SQLITE_OK!=ret){
printf("数据表查找房间号数据失败:%s\n",errmage);
return -2;
}
ret=sqlite3_exec(my_db,sql_func2,&my_callback_func2,&room.password,&errmage);
if(SQLITE_OK!=ret){
printf("数据表查找房间密码数据失败:%s\n",errmage);
return -2;
}
return 0;
}
//房间登录函数
int register_room_func(sqlite3 *my_db,room_t *room,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
memset(room,0,sizeof(room));
printf("请输入房间号>>");
scanf("%d",&room->room_number);
printf("请输入房间密码>>");
scanf("%d",&room->password);
int value=judge_room_func(my_db,*room,tablename);
int get_line(FILE *fp){
int ret=0;
int count=0;
while((ret=fgetc(fp))!=-1){
if(ret=='\n'){
count++;
}
}
return count;
}
if(0==value){
printf("登录成功,请开始您的聊天之旅...\n");
//创建日志文件,将用户登录信息写入日志
FILE *fp=fopen("room_register.txt","a+");
if(NULL==fp){
perror("fopen error");
return -1;
}
static time_t sec=0;
static time_t old_sec=0;
int line=get_line(fp);
time(&sec);
if(sec!=old_sec){
struct tm *my_time=localtime(&sec);
line++;
//拼接用户信息
fprintf(fp, "%d.%4d-%02d-%02d %02d:%02d:%02d:%3d\n", line, \
my_time->tm_year+1900, my_time->tm_mon+1, my_time->tm_mday, \
my_time->tm_hour, my_time->tm_min, my_time->tm_sec,room->room_number);
fflush(fp);//手动刷新缓冲区
old_sec = sec;
}
}else{
return -1;
}
return 0;
}
//注销房间函数
int delete_room_func(sqlite3 *my_db,room_t room,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
memset(&room,0,sizeof(room));
printf("请输入房间号>>");
scanf("%d",&room.room_number);
printf("请输入房间密码>>");
scanf("%d",&room.password);
int value=judge_room_func(my_db,room,tablename);
char *sql_roomr=sqlite3_mprintf("delete from %q where 房间号=%d",tablename,room.room_number);
char *errmage;
int ret=sqlite3_exec(my_db,sql_roomr,NULL,NULL,&errmage);
if(SQLITE_OK!=ret){
printf("数据表删除数据失败:%s\n",errmage);
return -2;
}
sqlite3_free(sql_roomr);
printf("注销房间成功...\n");
return 0;
}
//查看已有聊天室函数
int search_room_func(sqlite3 *my_db,const char *tablename){
if(NULL==my_db || NULL==tablename){
printf("入参失败,请检查...\n");
return -1;
}
int i=0;
int numb=0;
int j=1;
//回调函数
int my_callback_func(void *data,int argc,char **argv,char **zazColName){
//打印表头
if((*(int *)data)++==0){
printf("-");
for(i=0;i<argc;i++){
printf("%9s",zazColName[i]);
}
printf("-\n");
}
printf("[");
for(i=0;i<argc;i++){
printf("%8s",argv[i]);
}
printf("]\n");
return 0;
}
char *errmsg;
char *sql=sqlite3_mprintf("select *from %q",tablename);
//printf("%s\n",sql);
int ret=sqlite3_exec(my_db,sql,&my_callback_func,&numb,&errmsg);
if(SQLITE_OK!=ret){
printf("数据查找失败:%s\n",errmsg);
return -2;
}
sqlite3_free(sql);
printf("查找完成...\n");
return 0;
}
//查看聊天记录函数---针对所有聊天室
int search_room_history_func(){
char pathname[1024]="history.txt";
FILE *fp=fopen(pathname,"r");
if(NULL==fp){
perror("fopen error");
return -1;
}
char buf[512]={0};
while(fgets(buf,sizeof(buf),fp)!=NULL){
printf("%s\n",buf);
}
return 0;
}
//客户机启动函数
int client_user_func(int socket_fd){
//目标主机地址信息结构体
struct sockaddr_in severInfo={.sin_family=AF_INET,.sin_addr=inet_addr("192.168.250.100"),.sin_port=htons(6666)};
int socket_len=sizeof(severInfo);
//链接目标主机
int ret=connect(socket_fd,(struct sockaddr *)&severInfo,socket_len);
if(-1==ret){
perror("connect error:");
return -1;
}
printf("TCP客户端启动\n");
return 0;
}
1.2client_main.c
#include"client.h"
int main(int argc, char const *argv[])
{
#if 1
sqlite3 *my_database;
user_t user;
room_t room;
int ret=0;
printf("欢迎您使用网络聊天室...\n");
ret=sqlite3_open("my_epoll_chat.db",&my_database);
if(SQLITE_OK!=ret){
printf("创建数据库失败...\n");
exit(EXIT_FAILURE);
}
int option1=0;
int option2=0;
int option3=0;
int value=0;
char buf[4096]={0};
while(true){
print_menu_one_func();
printf("请输入您的选择>>");
scanf("%d",&option1);
if(3==option1){//用户退出
printf("欢迎您下次使用...\n");
exit(EXIT_SUCCESS);
}else if(2==option1){//用户注册
//注册用户则将信息写入数据库保存
//创建用户数据表
char *sql1="create table if not exists user1(用户名 text primary key,密码 int)";
create_tables_func(my_database,sql1);
//将用户信息写入数据表
insert_tables_user_func(my_database,user,"user1");
printf("恭喜您,注册成功...\n");
continue;
}else if(1==option1){//用户登录
//判断用户是否已注册
//printf("1\n");
value=register_user_func(my_database,&user,"user1");
if(0!=value){
continue;
}
while(true){
print_menu_two_func();
printf("请输入您的选择>>");
scanf("%d",&option2);
if(5==option2){//用户注销
delete_user_func(my_database,user,"user1");
printf("注销成功,期待您的再次使用...\n");
break;
}else if(4==option2){//退出
break;
}else if(3==option2){//查看已有聊天室
search_room_func(my_database,"room1");
}else if(2==option2){//注册聊天室
//注册房间则将信息写入数据库保存
//创建房间数据表
char *sql2="create table if not exists room1(房间号 int primary key,密码 int)";
create_tables_func(my_database,sql2);
//将用户信息写入数据表
insert_tables_room_func(my_database,room,"room1");
printf("恭喜您,注册成功...\n");
}else if(1==option2){//登录聊天室
//判断房间是否已注册
value=register_room_func(my_database,&room,"room1");
if(0!=value){
continue;
}
int socket_fd=socket(AF_INET,SOCK_STREAM,0);
if(-1==socket_fd){
perror("socket error");
exit(EXIT_FAILURE);
}
client_user_func(socket_fd);
while(true){
print_menu_three_func();
printf("请输入您的选择>>");
scanf("%d",&option3);
if(4==option3){//注销聊天室
delete_room_func(my_database,room,"room1");
printf("注销成功,期待您的再次使用...\n");
break;
}else if(3==option3){//退出
break;
}else if(2==option3){//聊天历史记录
//聊天历史记录由服务器记录,客户机查看
search_room_history_func();
}else if(1==option3){//聊天--客户机与服务器收发数据
while(true){
memset(buf,0,sizeof(buf));
//printf("1111\n");
printf("请输入要发送的数据>>");
fgets(buf,sizeof(buf),stdin);
//buf[strlen(buf)-1]='\0';
//printf("%s\n",buf);
//printf("2222\n");
int a=strcmp(buf,"quit\n");
//printf("%d\n",a);
if(!strcmp(buf,"quit\n")){
printf("已退出聊天...\n");
break;
}
int nbytes=write(socket_fd,buf,strlen(buf));
if(-1==nbytes){
perror("write errro:");
continue;
}
memset(buf,0,sizeof(buf));
nbytes=read(socket_fd,buf,sizeof(buf));
if(-1==nbytes){
perror("read error:");
exit(EXIT_FAILURE);
}
if(0==nbytes){
printf("服务器已关闭\n");
break;
}
printf("数据:%s\n",buf);
}
}
}
close(socket_fd);
}
}
}
}
#else
print_menu_one_func();
print_menu_two_func();
print_menu_three_func();
#endif
return 0;
}
1.3client.h
#ifndef _CLIENT_H
#define _CLIENT_H
#include<stdio.h>
#include<stdio.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<string.h>
#include<stdbool.h>
#include<sys/epoll.h>
#include<sqlite3.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
typedef struct _users{
char name[256];
int password;
}user_t;
typedef struct _rooms{
int room_number;
int password;
}room_t;
void print_menu_one_func();
void print_menu_two_func();
void print_menu_three_func();
int create_tables_func(sqlite3 *my_db,const char *sql);
int insert_tables_user_func(sqlite3 *my_db,user_t user,const char *tablename);
int judge_user_func(sqlite3 *my_db,user_t user,const char *tablename);
int delete_user_func(sqlite3 *my_db,user_t user,const char *tablename);
int register_user_func(sqlite3 *my_db,user_t *user,const char *tablename);
int insert_tables_room_func(sqlite3 *my_db,room_t room,const char *tablename);
int judge_room_func(sqlite3 *my_db,room_t room,const char *tablename);
int register_room_func(sqlite3 *my_db,room_t *room,const char *tablename);
int delete_room_func(sqlite3 *my_db,room_t room,const char *tablename);
int search_room_func(sqlite3 *my_db,const char *tablename);
int search_room_history_func();
int client_user_func(int socket_fd);
#endif
2.服务器
2.1sever.c
#include"sever.h"
//服务器启动函数
int sever_func(int socket_fd){
//绑定套接字
struct sockaddr_in severInfo={.sin_family=AF_INET,.sin_port=htons(6666),.sin_addr.s_addr=INADDR_ANY};
int socket_len=sizeof(severInfo);
int ret=bind(socket_fd,(struct sockaddr *)&severInfo,socket_len);
if(-1==ret){
perror("bind error");
return -1;
}
//将套接字设为监听状态
ret=listen(socket_fd,10);
if(-1==ret){
perror("listen error");
return -1;
}
printf("epoll---TCP服务器启动\n");
return 0;
}
//聊天历史记录函数
int create_user_history_func(const char *msg){
if(NULL==msg){
printf("入参失败,请检查...\n");
return -1;
}
char pathname[1024]="history.txt";
FILE *fp=fopen(pathname,"a+");
if(NULL==fp){
perror("fopen error");
return -1;
}
int get_line(FILE *fp){
int ret=0;
int count=0;
while((ret=fgetc(fp))!=-1){
if(ret=='\n'){
count++;
}
}
return count;
}
static time_t sec=0;
static time_t old_sec=0;
int line=get_line(fp);
time(&sec);
if(sec!=old_sec){
struct tm *my_time=localtime(&sec);
line++;
//拼接用户信息
fprintf(fp, "%d.%4d-%02d-%02d %02d:%02d:%02d---%s\n", line, \
my_time->tm_year+1900, my_time->tm_mon+1, my_time->tm_mday, \
my_time->tm_hour, my_time->tm_min, my_time->tm_sec,msg);
fflush(fp);//手动刷新缓冲区
old_sec = sec;
}
}
2.2sever_main.c
#include"sever.h"
int main(int argc, char const *argv[])
{
int socket_fd=socket(AF_INET,SOCK_STREAM,0);
if(-1==socket_fd){
perror("socket error");
exit(EXIT_FAILURE);
}
sever_func(socket_fd);
struct sockaddr_in clientInfo={.sin_family=AF_INET};
int client_len=sizeof(clientInfo);
//创建文件描述符集合体
int epoll_fd=epoll_create1(0);
if(-1==epoll_fd){
perror("epoll creat error");
exit(EXIT_FAILURE);
}
//创建事件结构体
struct epoll_event my_event={.events=EPOLLIN,.data.fd=socket_fd};
//将关心的文件描述符放入事件实例中
int ret=epoll_ctl(epoll_fd,EPOLL_CTL_ADD,socket_fd,&my_event);
if(-1==ret){
perror("epoll ctl add error");
exit(EXIT_FAILURE);
}
//创建就绪事件结构体数组
struct epoll_event my_user_revents[2048]={0};
int maxrevents=sizeof(my_user_revents)/sizeof(struct epoll_event);
//定义聊天室最大人数为200人
int my_fds=0;
int connect_fds[200]={0};
int nbtyes=0;
int i=0,j=0;
int connect_fd=0;
char buf[512]={0};
//进行数据循环收发
while(true){
int fds=epoll_wait(epoll_fd,my_user_revents,maxrevents,-1);
if(-1==fds){
perror("epoll_wait error");
exit(EXIT_FAILURE);
}
for(i=0;i<fds;i++){
//判断就绪事件类型
if(socket_fd==my_user_revents[i].data.fd){
connect_fd=accept(my_user_revents[i].data.fd,(struct sockaddr *)&clientInfo,&client_len);
if(-1==connect_fd){
perror("accept error");
continue;
}
printf("客户机IP:%s 端口:%d\n",inet_ntoa(clientInfo.sin_addr),ntohs(clientInfo.sin_port));
//将新产生的就绪事件放入实例中
my_event.data.fd=connect_fd;
my_event.events=EPOLLIN;
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,connect_fd,&my_event);
//将客户链接产生的fd放入到已连接fd数组中
if(my_fds<200){
connect_fds[my_fds]=connect_fd;
my_fds++;
}else{
const char errmsg[1024]="聊天室人数已满,请选择其他聊天室...\n";
nbtyes=write(connect_fd,errmsg,sizeof(errmsg));
if(-1==nbtyes){
perror("write error");
continue;
}
}
}else{
memset(buf,0,sizeof(buf));
nbtyes=read(my_user_revents[i].data.fd,buf,sizeof(buf));
if(-1==nbtyes){
perror("read error");
continue;
}
if(0==nbtyes){
printf("客户机已关闭\n");
//将此事件fd关闭
for(j=0;j<my_fds;j++){
if(connect_fds[j]==my_user_revents[i].data.fd){
my_fds--;
connect_fds[j]=connect_fds[my_fds];
connect_fds[my_fds]=-1 ;
}
}
//将就绪事件删除
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,my_user_revents[i].data.fd,&my_user_revents[i]);
close(my_user_revents[i].data.fd);
continue;
}
buf[strlen(buf)-1]='\0';
printf("客户端数据%s\n",buf);
create_user_history_func(buf);
for(j=0;j<my_fds;j++){
//不用向自己发送数据了
if(connect_fds[j]==my_user_revents[i].data.fd){
continue;
}
//向数组中所有套接字发送数据
nbtyes=write(connect_fds[j],buf,sizeof(buf));
if(-1==nbtyes){
perror("write error");
continue;
}
}
}
}
}
return 0;
}
2.4sever.h
#ifndef _SEVER_H
#define _SEVER_H
#include<stdio.h>
#include<stdio.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<string.h>
#include<stdbool.h>
#include<sys/epoll.h>
#include<sqlite3.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
int sever_func(int socket_fd);
int create_user_history_func(const char *msg);
#endif