一、项目介绍
该项目使用了基于TCP/IP协议的服务器/客户端模型,通过多路IO复用(该项目使用的是epoll机制)实现多用户登录。
epoll机制:
epoll(事件轮询)是Linux内核提供的一种高效的I/O事件通知机制,用于处理大量的并发连接。它是基于文件描述符的I/O多路复用技术之一,可以监视多个文件描述符上的事件,当文件描述符就绪时,通知应用程序进行相应的处理。
epoll机制相比于传统的select和poll机制具有更高的性能和扩展性,主要体现在以下几个方面:
-
支持大量的并发连接:epoll使用红黑树来存储文件描述符,可以高效地处理大量的并发连接。
-
只通知就绪的事件:epoll只通知应用程序已经就绪的文件描述符,避免了遍历整个文件描述符集合的开销。
-
边缘触发(Edge-Triggered)模式:epoll可以以边缘触发的模式工作,只在状态变化时通知应用程序,而不是在状态保持时一直通知。
-
支持跨平台:epoll是Linux特有的机制,但是其他类似的机制也存在于其他操作系统上,如kqueue在FreeBSD上。
服务器/客户端模型:
服务器框架:创建套接字,绑定IP端口信息,监听网络,允许连接,收/发消息,关闭套接字。
客户端框架:创建套接字,连接服务器,收/发消息,关闭套接字。
逻辑图:
数据库:
二、服务器代码实现
1、server.c
具体函数名及功能见注释
#include "server.h"
int main()
{
log_t =0;
fu_t = 0;
fr_t = 0;
//初始化数据库
ret = mysql_init(&mysql);
if(ret == NULL)
{
perror("mysql_init");
return -1;
}
//链接数据库
ret = mysql_real_connect(ret,"localhost","root","1","chat",0,0,0);
if(ret == NULL)
{
perror("mysql_real_connect");
return -1;
}
//创建套接字
int rt;
sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("sock");
return -1;
}
//设置套接字多路复用
int flag =1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));
//绑定端口
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(6666);
seraddr.sin_addr.s_addr = inet_addr("192.168.202.137");
rt = bind(sock,(struct sockaddr *)&seraddr,sizeof(seraddr));
if(rt < 0)
{
perror("bind");
return -1;
}
//监听网络
rt = listen(sock,20);
if(rt < 0)
{
perror("listen");
return -1;
}
//epoll
struct sockaddr_in cliaddr;
len = sizeof(cliaddr);
struct sockaddr_in addr_c[25];
//创建一个epoll文件描述符,用来监听其他文件描述符(套接字)
epid = epoll_create(1);
//将服务器的套接字加入epid监听
evt.events = EPOLLIN;
evt.data.fd = sock;
epoll_ctl(epid,EPOLL_CTL_ADD,sock,&evt);
while(1)
{
//监听文件描述符是否异动,返回移动端个数
count = epoll_wait(epid,myepoll,20,-1);
for(int i = 0; i < count; i++)
{
//是否产生异动
if(myepoll[i].events == EPOLLIN)
{
//是不是服务器本身的套接字产生异动,是则允许客户端链接,不是则处理客户请求
if(myepoll[i].data.fd == sock)
{
sock_c = accept(sock,(struct sockaddr *)&cliaddr,&len);
//将客户的套接字加入监听
evt.data.fd = sock_c;
evt.events = EPOLLIN;
addr_c[sock_c]=cliaddr;
epoll_ctl(epid,EPOLL_CTL_ADD,sock_c,&evt);
}
else
{
Inf inf;
memset(&inf,0,sizeof(inf));
//接收客户端消息,存到inf消息结构体中
if(read(myepoll[i].data.fd,&inf,sizeof(inf)) == 0)
{
//客户下线
close(myepoll[i].data.fd);
//数组内的套接字设置为0,表示用户下线,不能收到群聊消息
for(int j=0;j<log_t;j++)
{
if(loguse[j].sock == myepoll[i].data.fd)
{
for(int x=j;x<=log_t;x++)
{
loguse[x] = loguse[x+1];
}
}
}
log_t--;
//将下线的客户从数组中移出,方便判断是哪个用户正在发送文件
for(int j=0;j<fu_t;j++)
{
if(file_use[j].sock == myepoll[i].data.fd)
{
for(int x=j;x<=log_t;x++)
{
file_use[x] = file_use[x+1];
}
}
}
fu_t--;
//将下线的客户从数组中移出,方便判断要接受文件的用户是否在线
for(int j=0;j<fr_t;j++)
{
if(file_recv[j].sock == myepoll[i].data.fd)
{
for(int x=j;x<=log_t;x++)
{
file_recv[x] = file_recv[x+1];
}
}
}
fr_t--;
//将下线的客户从epid中移出,不在监听
epoll_ctl(epid,EPOLL_CTL_DEL,myepoll[i].data.fd,NULL);
}
else
{
//判断操作
if(inf.flg == '2')//注册
{
Register(ret,myepoll,addr_c[myepoll[i].data.fd],inf,i);
}
else if(inf.flg == '1')//登录
{
login(ret,myepoll,addr_c[myepoll[i].data.fd],inf,i);
}
else if(inf.flg == '3')//注销
{
logout(ret,myepoll,inf,i);
for(int j=0;j<log_t;j++)
{
if(loguse[j].sock == myepoll[i].data.fd)
{
for(int x=j;x<=log_t;x++)
{
loguse[x] = loguse[x+1];
}
}
}
log_t--;
for(int j=0;j<fu_t;j++)
{
if(file_use[j].sock == myepoll[i].data.fd)
{
for(int x=j;x<=log_t;x++)
{
file_use[x] = file_use[x+1];
}
}
}
fu_t--;
for(int j=0;j<fr_t;j++)
{
if(file_recv[j].sock == myepoll[i].data.fd)
{
for(int x=j;x<=log_t;x++)
{
file_recv[x] = file_recv[x+1];
}
}
}
fr_t--;
}
else if(inf.flg == '4')//私聊
{
memset(&send_s,0,sizeof(send_s));
int k,j;
//遍历在线用户数组,找到自身,将自己的用户名一起发给私聊用户,在客户端显示昵称
for ( k=0; k<log_t; k++)
{
if(loguse[k].sock==myepoll[i].data.fd)
{
sprintf(send_s.user,"%s",loguse[k].user);
sprintf(send_s.msg,"%s",inf.msg);
break;
}
}
//通过指定的用户名找到给客户发消息
for(j=0;j<log_t;j++)
{
if(strcmp(loguse[j].user, inf.user)==0)
{
send_s.flg = 1;
write(loguse[j].sock,&send_s,sizeof(send_s));
break;
}
}
//数组遍历完了,没有找到对应的用户,则证明用户不在线。
if(j==log_t)
{
send_s.flg = 1;
sprintf(send_s.msg,"用户%s不在线,输入quit退出",inf.user);
write(myepoll[i].data.fd,&send_s,sizeof(send_s));
}
}
else if(inf.flg == '5')//群聊
{
memset(&send_s,0,sizeof(send_s));
//通过套接字找到自己,群聊不给自己发
for (int k=0; k<log_t; k++)
{
if(loguse[k].sock==myepoll[i].data.fd)
{
sprintf(send_s.user,"%s",loguse[k].user);
sprintf(send_s.msg,"%s",inf.msg);
break;
}
}
//遍历数组,给每一个在线的用户都发消息
for(int j=0; j<log_t;j++)
{
if((loguse[j].sock==myepoll[i].data.fd) || (loguse[j].sock == 0))
{
continue;
}
send_s.flg =2;
write(loguse[j].sock,&send_s,sizeof(send_s));
}
}
//文件传输功能,一次1024字节
else if(inf.flg == '8')
{
rs_file(myepoll,inf,i);
}
}
}
}
}
}
return 0;
}
2、server.h
具体结构体定义,全局变量定义,结构体定义。(功能见注释)
#ifndef _SERVER_H_
#define _SERVER_H_
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/epoll.h>
#include <mysql/mysql.h>
//收消息结构体
typedef struct {
char flg;
char file_flg;
long size;
char log[20];
char pas[20];
char user[20];
char msg[100];
char filename[100];
char filemsg[1025];
}Inf;
//登录用户的结构体
typedef struct {
int sock;
char user[20];
}Loguse;
//发聊天消息的结构体
typedef struct {
int flg;
char user[20];
char msg[150];
}SEND;
//登录函数
void login(MYSQL * ret,struct epoll_event *myepoll,struct sockaddr_in cliaddr,Inf inf,int index);
//注册函数
void Register(MYSQL * ret,struct epoll_event *myepoll,struct sockaddr_in cliaddr,Inf inf,int x);
//注销函数
void logout(MYSQL * ret,struct epoll_event *myepoll,Inf inf,int index);
//文件传输
void rs_file(struct epoll_event *myepoll,Inf inf,int index);
//套接字
int sock,sock_c;
//服务器的IP端口信息结构体
struct sockaddr_in seraddr;
socklen_t len;
//产生异动的套接字结构体数组
struct epoll_event myepoll[20];
//epid的信息结构体
struct epoll_event evt;
//epoll文件描述符
int epid;
//产生异动的文件描述符个数
int count;
MYSQL_RES * res;//数据库结构体,输出的信息
MYSQL mysql;//初始化数据库的核心结构体
MYSQL *ret;//数据库的核心结构体
int row,line;//数据库的行、列数
Loguse loguse[20];//在线用户结构体数组
int log_t;//数组下标
//给客户端发消息的结构体
SEND send_s;
Loguse file_use[20];//发文件用户结构体数组
int fu_t;
Loguse file_recv[20];//收文件用户结构体
int fr_t;
#endif
3、func_ser.c
功能函数的实现:
登录函数:用户登录,查询数据库,比对账户密码,登录成功则将用户的信息(套接字,用户名)存入信息结构体;登录失败则给客户端返回失败信息;
注册函数:将用户的账户,密码,用户名,IP地址,存入数据库。存入前判断先查询数据库,判断用户是否存在,并给客户端返回相应信息。
注销函数:账户注销,将用户的信息从结构体中删除,并给客户端返回信息。
文件传输:判断用户是要接收或是发送文件,接收文件则将用户信息存入相应的结构体数组;发送也是如此,并且遍历数组,通过用户名找到相应的套接字,实现用户间的文件传输。
私聊群聊:此功能在server.c文件中实现,具体介见注释。
#include "server.h"
//注册函数
void Register(MYSQL * ret,struct epoll_event *myepoll,struct sockaddr_in cliaddr,Inf inf,int x)
{
struct sockaddr_in cli=cliaddr;
char query[100];
//拼接mysql命令,查询数据库
sprintf(query,"select * from chat_user where 账户=\"%s\" or 用户名=\"%s\";",inf.log,inf.user);
mysql_query(ret,query);
//储存结果集合
res = mysql_store_result(ret);
if(res == NULL)
{
perror("mysql_store_result");
return ;
}
//获取行,获取到了,则用户存在
row = mysql_num_rows(res);
if(row != 0)
{
write(myepoll[x].data.fd,"该账户存在",20);
mysql_free_result(res);
}
//不存在则插入数据库
else
{
mysql_free_result(res);
char query_[100];
sprintf(query_,"insert into chat_user values(\"%s\",\"%s\",\"%s\",\"%s\");",inf.log,inf.pas,inet_ntoa(cli.sin_addr),inf.user);
mysql_query(ret,query_);
write(myepoll[x].data.fd,"注册成功",20);
}
}
//登录函数
void login(MYSQL * ret,struct epoll_event *myepoll,struct sockaddr_in cliaddr,Inf inf,int index)
{
int x = index;
struct sockaddr_in cli=cliaddr;
Inf inf_c = inf;
SEND se;
//拼接字符串,通过用户名和密码查询数据库。
char query[100]={0};
sprintf(query,"select * from chat_user where 账户=\"%s\" and 密码=\"%s\";",inf_c.log,inf_c.pas);
mysql_query(ret,query);
//储存结果集合
res = mysql_store_result(ret);
if(res == NULL)
{
perror("mysql_store_result");
return ;
}
row = mysql_num_rows(res);
//执行查询语句后,查询到了。则登录成功。
if(row != 0)
{
//获取当前行的第四个元素,存的是用户名,
MYSQL_ROW myrow;
myrow=mysql_fetch_row(res);
sprintf(inf_c.user,"%s",myrow[3]);
mysql_free_result(res);
//给客户端返回信息,“登录成功+用户名””
sprintf(se.msg,"%s","登录成功");
sprintf(se.user,"%s",inf_c.user);
write(myepoll[x].data.fd,&se,sizeof(se));
//更新ip地址
char que[100]={0};
sprintf(que,"update chat_user set ip地址=\"%s\" where 账户=\"%s\";",inet_ntoa(cli.sin_addr),inf_c.log);
mysql_query(ret,que);
//将登录的用户加入登录用户数组,方便实现私聊群聊
sprintf(loguse[log_t].user, "%s", inf_c.user);
loguse[log_t].sock = myepoll[x].data.fd;
log_t++;
}
else
{
mysql_free_result(res);
sprintf(se.msg,"%s","账户或密码错误");
write(myepoll[x].data.fd,&se,sizeof(se));
}
}
//注销函数
void logout(MYSQL * ret,struct epoll_event *myepoll,Inf inf,int index)
{
int x = index;
char query[100]={0};
sprintf(query,"select * from chat_user where 账户=\"%s\" and 密码=\"%s\";",inf.log,inf.pas);
mysql_query(ret,query);
//储存结果集合
res = mysql_store_result(ret);
if(res == NULL)
{
perror("mysql_store_result");
return ;
}
row = mysql_num_rows(res);
if(row != 0)
{
mysql_free_result(res);
char que[100]={0};
sprintf(que,"delete from chat_user where 账户=\"%s\";",inf.log);
mysql_query(ret,que);
write(myepoll[x].data.fd,"账户已注销",20);
}
else
{
write(myepoll[x].data.fd,"账户不存在",20);
mysql_free_result(res);
}
}
void rs_file(struct epoll_event *myepoll,Inf inf,int index)
{
int x = index;
Inf send_i;
if(inf.file_flg == '1')//发送文件
{
//遍历数组,找到正在发送用户的用户名;
file_use[fu_t].sock = myepoll[x].data.fd;
for(int j = 0;j<log_t;j++)
{
if(myepoll[x].data.fd == loguse[j].sock)
{
strcpy(file_use[fu_t].user,loguse[j].user);
fu_t++;
strcpy(send_i.user,loguse[j].user);
break;
}
}
int k;
//遍历数组,找到正要接收文件的用户的id(套接字);用户不在数组内,则还没做好接收准备,不传输。
for( k = 0;k<fr_t;k++)
{
if(strcmp(inf.user,file_recv[k].user) == 0)
{
strcpy(send_i.filename,inf.filename);//文件名
strcpy(send_i.filemsg,inf.filemsg);//一次传输的文件内容
send_i.size = inf.size;//传输的大小
//一次传输1024字节,
if(inf.size<1024)
{
//最后一次的时候,给客户端回复
strcpy(send_i.msg,"文件接收成功");
write(myepoll[x].data.fd,&send_i,sizeof(send_i));
}
write(file_recv[k].sock,&send_i,sizeof(send_i));
break;
}
}
if(k == fr_t)
{
strcpy(send_i.msg,"用户不在线");
write(myepoll[x].data.fd,&send_i,sizeof(send_i));
}
}
else if(inf.file_flg == '2')//接收文件
{
//将信息存入数组,等待接收文件
file_recv[fr_t].sock = myepoll[x].data.fd;
for(int j = 0;j<log_t;j++)
{
if(myepoll[x].data.fd == loguse[j].sock)
{
strcpy(file_recv[fr_t].user,loguse[j].user);
break;
}
}
fr_t++;
}
//发送文件的客户出问题,则中断传输,并回复接收文件的客户
else if(inf.file_flg == '0')
{
strcpy(send_i.msg,"用户接收失败");
for(int j;j<fu_t;j++)
{
if(inf.user == file_use[j].user)
{
write(file_recv[j].sock,&send_i,sizeof(send_i));
}
}
}
}
三、客户端代码实现
1、client.h
#ifndef _CLIENT_H_
#define _CLIENT_H_
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
typedef struct {
char flg;
char file_flg;
long size;
char log[20];
char pas[20];
char user[20];
char msg[100];
char filename[100];
char filemsg[1025];
}Inf;//消息结构体,与服务端进行交互时使用
typedef struct {
int flg;
char user[20];
char msg[150];
}REVC;//消息结构体,承接服务端的返回信息,输出服务端的返回信息时用。
//可视化菜单函数
void meu_1();
void meu_2();
void Register(int sock,char ch);//登录
void login(int sock,char ch);//注册
void logout(int sock,char c);//注销
//私聊,群聊实现函数
void change();//功能选择
void *myfun(void *arg);//线程回调函数,群聊
void *myfun_1(void *arg);//线程回调函数,私聊
void union_chat(char ch);//群聊功能
void ind_chat(char ch);//私聊功能
//查看本地聊天记录
void check_i();
void check_u();
//文件收发
void rs_file(char ch);
void send_file(char ch,char avl);
void recv_file(char ch,char avl);
int sock;//套接字
struct sockaddr_in seraddr;//
pthread_t pid, pid_1;//多线程文件描述符
REVC revc_s;
char user_f[20];//文件收发用户
//
int flag;
int flag_1;
#endif
2、client.c
#include "client.h"
int main()
{
flag =0;
flag_1 =0;
int ret;
//创建套接字
sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
return -1;
}
//链接服务器
seraddr.sin_family = AF_INET;
seraddr.sin_port= htons(6666);
seraddr.sin_addr.s_addr = inet_addr("192.168.202.137");
ret = connect(sock,(struct sockaddr *)&seraddr,sizeof(seraddr));
if(ret < 0)
{
perror("connect");
return -1;
}
char ch;
while(1)
{
meu_1();
scanf("%c",&ch);
getchar();
switch(ch)
{
case '1'://登录
login(sock,ch);
break;
case '2'://注册
Register(sock,ch);
break;
case '3'://注销
logout(sock,ch);
break;
case '0'://退出
return 0;
default:
break;
}
}
close(sock);
return 0;
}
3.fun_cli.c
#include "client.h"
void meu_1()
{
printf("================\n");
printf("=====0.退出=====\n");
printf("=====1.登录=====\n");
printf("=====2.注册=====\n");
printf("=====3.注销=====\n");
printf("================\n");
}
void meu_2()
{
printf("====================\n");
printf("=== 0.退出 ===\n");
printf("=== 4.私聊 ===\n");
printf("=== 5.群聊 ===\n");
printf("===6.私聊聊天记录===\n");
printf("===7.群聊聊天记录===\n");
printf("=== 8.文件传输 ===\n");
printf("====================\n");
}
//注册
void Register(int sock,char ch)
{
char pas_1[20];
char buf[20];
Inf inf;
inf.flg=ch;
printf("请输入账号:\n");
scanf("%s",inf.log);
getchar();
printf("请输入用户名:\n");
scanf("%s",inf.user);
getchar();
printf("请输入密码:\n");
P: scanf("%s",inf.pas);
getchar();
printf("请确认密码:\n");
scanf("%s",pas_1);
getchar();
if(strcmp(pas_1,inf.pas)==0)
{
write(sock,&inf,sizeof(inf));
read(sock,buf,20);
printf("%s\n",buf);
}
else
{
printf("请重新输入密码:\n");
goto P;
}
}
//登录
void login(int sock,char ch)
{
REVC re;
Inf inf;
inf.flg = ch;
printf("请输入账号:\n");
scanf("%s",inf.log);
getchar();
printf("请输入密码:\n");
scanf("%s",inf.pas);
getchar();
write(sock,&inf,sizeof(inf));
read(sock,&re,sizeof(re));
sprintf(user_f,"%s",re.user);
printf("%s\n",re.msg);
if(strcmp(re.msg,"登录成功")==0)
{
change();
}
}
//注销
void logout(int sock,char c)
{
char buf[20];
Inf inf;
inf.flg = c;
printf("请输入要注销的账号:\n");
scanf("%s",inf.log);
getchar();
printf("请输入密码:\n");
scanf("%s",inf.pas);
getchar();
write(sock,&inf,sizeof(inf));
read(sock,buf,20);
printf("%s\n",buf);
}
//聊天功能选择
void change()
{
while(1)
{
meu_2();
char ch;
scanf("%c",&ch);
getchar();
switch(ch)
{
case '0':
return;
case '4':
ind_chat(ch);
break;
case '5':
union_chat(ch);
break;
case '6':
check_i();
break;
case '7':
check_u();
break;
case '8':
rs_file(ch);
break;
default:
break;
}
}
}
//线程收群消息
void *myfun(void *arg)
{
while(1)
{
memset(&revc_s,0,sizeof(revc_s));
if(read(sock,&revc_s,sizeof(revc_s))==0)
{
printf("服务器出错\n");
break;
}
if(revc_s.flg == 2)
{
FILE *fd = NULL;
fd = fopen("./union_chat.txt","a+");
fprintf(fd,"%s %s %s\n",revc_s.user,":",revc_s.msg);
fclose(fd);
printf("%s:%s\n",revc_s.user,revc_s.msg);
}
else if((flag == 1)&&(revc_s.flg==1))
{
FILE *fid = NULL;
fid = fopen("./chat_s.txt","a+");
fprintf(fid,"%s %s %s\n",revc_s.user,":",revc_s.msg);
fclose(fid);
}
}
}
//群聊函数
void union_chat(char ch)
{
Inf inf;
char use[20];
char c[4];
flag = 1;
printf("您已进入群聊模式\n输入AED退出群聊\n");
if(flag_1 == 1)
{
FILE *p = fopen("./chat.txt","r");
if(p==NULL)
{
goto P1;
}
while(fscanf(p,"%s %s %s",use,c,inf.msg) == 3)
{
printf("%s %s %s\n",use,c,inf.msg);
}
fclose(p);
remove("chat.txt");
memset(inf.msg,0,sizeof(inf.msg));
}
P1: flag_1 =2;
pthread_create(&pid,NULL,myfun,NULL);
while(1)
{
inf.flg =ch;
p: scanf("%s",inf.msg);
getchar();
if(strlen(inf.msg) > 99)
{
printf("消息超过字数限制,发送失败\n");
goto p;
}
else if(strcmp("AED",inf.msg) == 0)
{
flag_1 = 1;
pthread_cancel(pid);
pthread_join(pid,NULL);
return;
}
else
{
FILE *fp = fopen("./union_chat.txt","a+");
fprintf(fp,"%s %s %s\n",user_f,":",inf.msg);
fclose(fp);
write(sock,&inf,sizeof(inf));
}
}
}
//线程收私聊消息
void *myfun_1(void *arg)
{
while(1)
{
memset(&revc_s,0,sizeof(revc_s));
if(read(sock,&revc_s,sizeof(revc_s))==0)
{
printf("服务器出错\n");
break;
}
if(revc_s.flg == 1)
{
FILE *fd = NULL;
fd = fopen("./ind_chat.txt","a+");
fprintf(fd,"%s %s %s\n",revc_s.user,":",revc_s.msg);
fclose(fd);
printf("%s:%s\n",revc_s.user,revc_s.msg);
}
else if((flag_1 == 1)&&(revc_s.flg ==2))
{
FILE *fid = NULL;
fid = fopen("./chat.txt","a+");
fprintf(fid,"%s %s %s\n",revc_s.user,":",revc_s.msg);
fclose(fid);
}
}
}
//私聊函数
void ind_chat(char ch)
{
Inf inf;
char use[20];
char c[4];
flag_1 =1;
printf("您已进入私聊模式\n");
if(flag ==1)
{
FILE * p = fopen("chat_s.txt","r");
if(p==NULL)
{
goto F;
}
while(fscanf(p,"%s %s %s",use,c,inf.msg) == 3)
{
printf("%s %s %s\n",use,c,inf.msg);
}
fclose(p);
remove("chat_s.txt");
memset(inf.msg,0,sizeof(inf.msg));
}
F: flag = 2;
pthread_create(&pid_1,NULL,myfun_1,NULL);
while(1)
{
inf.flg =ch;
printf("输入要私聊的用户名\n(AED退出)\n");
scanf("%s",inf.user);
getchar();
if(strcmp(inf.user, "AED")==0)
{
flag = 1;
pthread_cancel(pid_1);
pthread_join(pid_1,NULL);
return;
}
while(1)
{
printf("请输入(quit退出):\n");
p: scanf("%s",inf.msg);
getchar();
if(strlen(inf.msg) > 99)
{
printf("消息超过字数限制,发送失败\n");
goto p;
}
else if(strcmp("quit",inf.msg) == 0)
{
break;
}
else
{
FILE *fp = fopen("./ind_chat.txt","a+");
fprintf(fp,"%s %s %s\n",user_f,":",inf.msg);
fclose(fp);
write(sock,&inf,sizeof(inf));
}
}
}
}
//查看群聊聊天记录
void check_u()
{
char ch[4];
REVC re;
FILE *fd = NULL;
fd = fopen("./union_chat.txt","r");
if(fd == NULL)
{
perror("fopen");
}
while(fscanf(fd,"%s %s %s",re.user,ch,re.msg) ==3)
{
printf("%s%s%s",re.user,ch,re.msg);
printf("\n");
}
fclose(fd);
}
//查看私聊聊天记录
void check_i()
{
char ch[4];
REVC re;
FILE *fd = NULL;
fd = fopen("./ind_chat.txt","r");
if(fd == NULL)
{
perror("fopen");
return;
}
while(fscanf(fd,"%s %s %s",re.user,ch,re.msg) ==3)
{
printf("%s%s%s",re.user,ch,re.msg);
printf("\n");
}
fclose(fd);
}
void rs_file(char ch)
{
char avl;
char ch1 =ch;
printf("1发送文件\n");
printf("2接收文件\n");
scanf("%c",&avl);
getchar();
switch(avl)
{
case '1':
send_file(ch1,avl);
break;
case '2':
recv_file(ch1,avl);
break;
default:
break;
}
}
//文件传输
void send_file(char ch, char avl)
{
Inf inf;
char bot[30];
char buf[150];
long read_count;
memset(&inf,0,sizeof(inf));
inf.flg = ch;
inf.file_flg = avl;
printf("请输入要传输的用户名:\n");
scanf("%s",inf.user);
getchar();
printf("请输入要传输的文件名:\n");
scanf("%s",inf.filename);
getchar();
printf("请输入文件的路径:\n");
scanf("%s",bot);
getchar();
sprintf(buf,"%s%s",bot,inf.filename);
FILE * fp = fopen(buf,"rb");
if(fp == NULL)
{
perror("fopen");
return;
}
while(1)
{
read_count = fread(inf.filemsg,1,1024,fp);
printf("%s\n",inf.filemsg);
inf.size = read_count;
write(sock,&inf,sizeof(inf));
if(read_count < 1024)
{
break;
}
}
read(sock,&inf,sizeof(inf));
printf("%s\n",inf.msg);
fclose(fp);
}
void recv_file(char ch,char avl)
{
char buf[150];
Inf inf;
char bot[30];
inf.flg = ch;
inf.file_flg = avl;
write(sock,&inf,sizeof(inf));
printf("请输入你想存在哪个目录:\n");
scanf("%s",bot);
getchar();
while(1)
{
read(sock,&inf,sizeof(inf));
sprintf(buf,"%s%s",bot,inf.filename);
FILE * fp = fopen(buf,"ab+");
if(fp == NULL)
{
Inf sd;
perror("fopen");
sd.file_flg = '0';
strcpy(sd.user,inf.user);
write(sock,&sd,sizeof(sd));
break;
}
fwrite(inf.filemsg,inf.size,1,fp);
if(inf.size < 1024)
{
printf("%s\n",inf.msg);
fclose(fp);
break;
}
}
}