聊天室(0)-项目源码-李兆龙
引言
假期生活马上就要结束了 其中最有意思的一个项目就要数这个聊天室 在进行这个项目的过程中接触到了很多新的知识和以前没有注意到的隐晦的知识点 所以分为三篇对项目进行一个总结 方便日后查阅和反思
- 项目源码
- 项目分析
- 项目文档
Data.h
#include
#include #define SERV_POT 8888 //服务器端口
#define LISTENQ 12 //连接请求队列的最大长度#define IP (127.0.0.1) //本地ip#define MESSADES_PAGE_SIZE 15 //好友消息分页最大数
#define FRIEND_PAGE_SIZE 5 //好友列表分页最大数
#define EVENTS_MAX_SIZE 10240 //epoll接收事件最大数
#define MAX_CONTECT_SIZE 10240 //服务器最大连接数
#define MAX_PATH_NAME 255 //文件名最长255 路径最长4096
#define MAX_USERNAME 64 //名称长度最大数
#define MAX_ACCOUNT 32 //账号长度最大值
#define MAX_PASSWORD 32 //密码最大长度
#define MAX_TELEPHONE 32 //找回密码使用的电话最长长度
#define MESSAGE_FETCH_SEND 1 //在登录时加载消息 消息类型为
#define MESSAGE_FETCH_RECV 2#define MAX_RECV 8096 //发包的大小 与接包大小相同#define ERROR_IN_LOGIN '@' //登录失败
#define INVAILD 'n' //用户信息无效
#define VAILD 'y' //用户信息有效
#define USERNAME 0
#define PASSWORD 1
#define BOX_NO_MESSAGES "@@@@@@@@@@@@@" //无文件标识符
#define BOX_HAVE_MESSAGS "$$$$$$$$$$$$$" //有文件标识符
#define OWNER 1 //群主
#define ADMIN 2 //管理员
#define COMMON 3 //群员
/*-----------------------------------------------*/
#define NULL_OF_GROUP -2 //群数量为零的特殊结束符号 用于login
#define EOF_OF_BOX -1 //发包结束符
#define LOGIN 0 //登录请求
#define REGISTER 1 //注册请求
#define RETRIEVE 2 //找回密码
#define ADD_FRIENDS 3 //添加好友
#define ADD_FRIENDS_QUERY 4 //收到添加好友请求后做出答复的数据类型
#define DEL_FRIENDS 5 //删除好友
#define LIST_FRIENDS 6 //显示好友列表
#define SEND_MESSAGES 7 //单聊 发送信息
#define REGISTER_GROUP 8 //注册群
#define ADD_GROUP 9 //加入群
#define QUIT 10 //退出群
#define DISSOLVE 11 //解散群
#define SET_ADMIN 12 //设置管理员
#define KICKING 13 //踢人
#define SEND_GROUP_MESSAGES 14 //群消息
#define SEND_FILE 15 //发送文件
#define RECV_FILE 16 //客户端收文件/*-----------------------------------------------*/typedef struct {int type; //事件类型int send_fd;char send_Account[MAX_ACCOUNT]; //账号 用账号来区分不同的人int recv_fd;char recv_Acount[MAX_ACCOUNT]; //char message[MAX_RECV];char message_tmp[MAX_RECV];//放两个缓冲区 以备不时之需int epfd;int conn_fd;
}recv_t; //客户端第一次recv中包的格式typedef struct
{char usename[MAX_USERNAME];char account[MAX_ACCOUNT];char message[MAX_RECV];int type; //事件类型
}Box_t;typedef struct message_node
{int type; //好友请求 单聊记录char send_account[MAX_ACCOUNT]; //朋友账号char recv_account[MAX_ACCOUNT]; //自己账号char nickname[MAX_USERNAME]; //朋友昵称char messages[MAX_RECV];struct message_node *next;struct message_node *prev;
}node_messages_t,*list_messages_t;typedef struct friend_node//好友链表类型
{int status;//是否在线char nickname[MAX_USERNAME];//昵称char recv_account[MAX_ACCOUNT];//朋友账号 接收者char send_account[MAX_ACCOUNT];//本人账号 发送者struct friend_node *next;struct friend_node *prev;//不需要加套接字 在服务器会进行查询
}node_friend_t,*list_friend_t;typedef struct group_node //群链表
{char nickname[MAX_USERNAME];//群名称char account[MAX_ACCOUNT];//群账号struct group_node *next;struct group_node *prev;
}node_group_t,*list_group_t;typedef struct member_node//群成员链表
{char nickname[MAX_USERNAME]; //昵称char account[MAX_ACCOUNT]; //账号int type; //标记位struct member_node *next;struct member_node *prev;
}node_member_t,*list_member_t;typedef struct group_messages_node//群消息链表
{char nickname[MAX_USERNAME]; //昵称char account[MAX_ACCOUNT]; //账号char messages[MAX_RECV]; //消息内容int type; //标记位struct group_messages_node *next;struct group_messages_node *prev;
}node_group_messages_t,*list_group_messages_t;typedef struct status_node //在服务器使用
{int status;char account[MAX_ACCOUNT];int fdd; //用来判断是否在线struct status_node *next;struct status_node *prev;
}node_status_t,*list_status_t;typedef struct //把一个结构体当做参数传入服务器传输线程
{char count[MAX_ACCOUNT];char path[256];
}recv_file_t;//包含分页器与链表操作
#define List_Init(list, list_node_t) { \list=(list_node_t*)malloc(sizeof(list_node_t)); \(list)->next=(list)->prev=list; \}#define List_Free(list, list_node_t) { \assert(NULL!=list); \list_node_t *tmpPtr; \(list)->prev->next=NULL; \while(NULL!=(tmpPtr=(list)->next)){ \(list)->next=tmpPtr->next; \free(tmpPtr); \} \(list)->next=(list)->prev=list; \}#define List_Destroy(list, list_node_t) { \assert(NULL!=list); \List_Free(list, list_node_t) \free(list); \(list)=NULL; \}#define List_AddHead(list, newNode) { \(newNode)->next=(list)->next; \(list)->next->prev=newNode; \(newNode)->prev=(list); \(list)->next=newNode; \}#define List_AddTail(list, newNode) { \(newNode)->prev=(list)->prev; \(list)->prev->next=newNode; \(newNode)->next=list; \(list)->prev=newNode; \}#define List_InsertBefore(node, newNode) { \(newNode)->prev=(node)->prev; \(newNode)->next=node; \(newNode)->prev->next=newNode; \(newNode)->next->prev=newNode; \}#define List_InsertAfter(node, newNode) { \(newNode)->next=node->next; \(newNode)->prev=node; \(newNode)->next->prev=newNode; \(newNode)->prev->next=newNode; \}#define List_IsEmpty(list) ((list != NULL) \&& ((list)->next == list) \&& (list == (list)->prev))#define List_DelNode(node) {\assert(NULL!=node && node!=(node)->next && node!=(node)->prev); \(node)->prev->next=(node)->next; \(node)->next->prev=(node)->prev; \}#define List_FreeNode(node) { \List_DelNode(node); \free(node); \}#define List_ForEach(list, curPos) \for ( curPos = (list)->next; \curPos != list; \curPos=curPos->next \)//分页器
typedef struct
{int totalRecords;int offset;int pageSize; void *curPos;
}Pagination_t;#define List_Paging(list, paging, list_node_t) { \if(paging.offset+paging.pageSize>=paging.totalRecords){ \Paging_Locate_LastPage(list, paging, list_node_t); }\else { \int i; \list_node_t * pos=(list)->next; \for( i=0; inext; \paging.curPos=(void*)pos; \} \} #define Paging_Locate_FirstPage(list, paging) { \paging.offset=0; \paging.curPos=(void *)((list)->next); \}#define Paging_Locate_LastPage(list, paging, list_node_t) { \int i=paging.totalRecords % paging.pageSize; \if (0==i && paging.totalRecords>0) \i=paging.pageSize; \paging.offset=paging.totalRecords-i; \list_node_t * pos=(list)->prev; \for(;i>1;i--) \pos=pos->prev; \paging.curPos=(void*)pos; \\
}#define Paging_ViewPage_ForEach(list, paging, list_node_t, pos, i) \for (i=0, pos = (list_node_t *) (paging.curPos); \pos != list && i < paging.pageSize; \i++, pos=pos->next) \
#define Paging_Locate_OffsetPage(list, paging, offsetPage, list_node_t) {\int offset=offsetPage*paging.pageSize; \list_node_t *pos=(list_node_t *)paging.curPos; \int i; \if(offset>0){ \if( paging.offset + offset >= paging.totalRecords ) {\Paging_Locate_LastPage(list, paging, list_node_t); \}else { \for(i=0; inext; \paging.offset += offset; \paging.curPos= (void *)pos; \} \}else{ \if( paging.offset + offset <= 0 ){ \Paging_Locate_FirstPage(list, paging); \}else { \for(i=offset; i<0; i++ ) \pos = pos->prev; \paging.offset += offset; \paging.curPos= pos; \} \} \
} #define Pageing_CurPage(paging) (0==(paging).totalRecords?0:1+(paging).offset/(paging).pageSize)#define Pageing_TotalPages(paging) (((paging).totalRecords%(paging).pageSize==0)?\(paging).totalRecords/(paging).pageSize:\(paging).totalRecords/(paging).pageSize+1)#define Pageing_IsFirstPage(paging) (Pageing_CurPage(paging)<=1)#define Pageing_IsLastPage(paging) (Pageing_CurPage(paging)>=Pageing_TotalPages(paging))
因为在在聊天室中事件类型非常繁杂,所以我们最好可以对所有的事件类型进行一一分析,分别定义其宏定义,方便在服务器端和客户端进行事件分析,从而正确的处理时间类型。下面的宏函数是课设时保留的链表操作函数与分页器函数。
client.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"solve.h" int fffffff;
int main()
{List_Init(status_per,node_status_t);MYSQL mysql; mysql_init(&mysql); //初始化一个句柄 mysql_library_init(0,NULL,NULL);//初始化数据库mysql_real_connect(&mysql,"127.0.0.1","root","lzl213260C","Login_Data",0,NULL,0);//连接数据库mysql_set_character_set(&mysql,"utf8");//调整为中文字符signal(SIGPIPE,SIG_IGN); //ctrl+c stop int sock_fd,conn_fd;int optval;int flag_recv=USERNAME;int name_num;pid_t pid;socklen_t cli_len;struct sockaddr_in cli_addr,serv_addr;size_t ret;int connect_size=0; //目前连接数 pthread_t pth1;recv_t recv_buf;int epfd,nfds;struct epoll_event ev,events[EVENTS_MAX_SIZE];sock_fd=socket(AF_INET,SOCK_STREAM,0); //服务器套接字if(sock_fd < 0){perror("socket");exit(1);}optval=1; //通用套接字 socket退出后可正常连接 待设置的套接字选项的值及其大小if(setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,(void*)&optval,sizeof(int))<0){perror("setsocket\n");exit(1);}setsockopt(sock_fd,SOL_SOCKET,SO_KEEPALIVE,(void*)&optval,sizeof(int));memset(&serv_addr,0,sizeof(struct sockaddr_in));serv_addr.sin_family=AF_INET; //协议族 ipv4 tcp/ip serv_addr.sin_port=htons(SERV_POT);//服务器端口 serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); //IP地址if(bind(sock_fd,(struct sockaddr*)&serv_addr,sizeof(struct sockaddr_in))<0){perror("bind\n");exit(1);}if(listen(sock_fd,LISTENQ)<0){perror("listen\n");exit(1);}epfd=epoll_create(1);ev.data.fd= sock_fd;ev.events =EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLRDHUP ; //设置为监听读的状态//使用默认的LT模式 // epoll 事件只触发一次epoll_ctl(epfd,EPOLL_CTL_ADD,sock_fd,&ev);connect_size++;for(;;){ nfds = epoll_wait(epfd,events,EVENTS_MAX_SIZE,-1);//等待可写事件for(int i=0;i<nfds;i++){//printf(" The event type is %d\n",events[i].events);connect_size++;if(events[i].data.fd==sock_fd) //服务器套接字接收到一个连接请求{if(connect_size>MAX_CONTECT_SIZE){perror("到达最大连接数!\n");continue;}conn_fd=accept(events[i].data.fd,(struct sokcaddr*)&cli_addr,&cli_len);//网络字节序转换成字符串输出printf("%d accept a new client ! ip:%s\n",++fffffff,inet_ntoa(cli_addr.sin_addr));if(conn_fd<=0){perror("error in accept\n");printf("%s\n",strerror(errno));continue;}ev.data.fd= conn_fd;ev.events =EPOLLIN | EPOLLONESHOT | EPOLLRDHUP;//|EPOLLOUT; //设置事件可写与可写epoll_ctl(epfd,EPOLL_CTL_ADD,conn_fd,&ev); //新增服务器套接字}else if(events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)){//客户端被挂掉 值设置为24 不把事件类型设置为SO_REUSEADDR 会发送很多可读事件//不清楚为什么 有点烦epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,0); close(events[i].data.fd);}else if(events[i].events & EPOLLIN ) //接收到可读 且不是服务器套接字 不用判断 上面已判断 {if((ret=recv(events[i].data.fd,&recv_buf,sizeof(recv_buf),MSG_WAITALL))<0) //接收//包的格式已经提前制定好{perror("recv\n");continue;}if(!ret)//防止客户端异常退出时无法改变状态 客户端异常时会先发送一个大小为零的包{ //对端关闭会发送一个可读事件 但包的大小为一//这个处理限定了一个IP只能登录一个账号list_status_t curps;char buf_tmp[256];List_ForEach(status_per,curps){//printf("一次n");if(curps->fdd==events[i].data.fd){sprintf(buf_tmp,"update Data set status = '0' where Account = '%s'",curps->account);//printf("%s\n",buf_tmp);mysql_query(&mysql,buf_tmp); //改变登录状态List_DelNode(curps); //不正常退出修改状态信息break;}}char buf[128];printf("The client with IP %d is disconnected\n",events[i].data.fd);sprintf(buf,"select *from Data where send_recv_fd = %d",events[i].data.fd);mysql_query(&mysql,buf);MYSQL_RES *result = mysql_store_result(&mysql);MYSQL_ROW row=mysql_fetch_row(result);mysql_free_result(result);sprintf(buf,"update Data set status = \"0\" where send_recv_fd = \"%d\"",events[i].data.fd);mysql_query(&mysql,buf);mysql_free_result(result);continue; }if(recv_buf.type==LOGIN){list_status_t tmp=(list_status_t)malloc(sizeof(node_friend_t));tmp->fdd=events[i].data.fd;//发送者套接字//printf("套接字%d\n",events[i].data.fd);strcpy(tmp->account,recv_buf.send_Account); //连接者账号 //用来在删除时找到账号修改状态List_AddTail(status_per,tmp); //加入在线者链表}recv_buf.send_fd = events[i].data.fd; //发送者的套接字已经改变 应转换为accept后的套接字recv_t *temp=(recv_t*)malloc(sizeof(recv_t)); //防止多线程访问一个结构体*temp=recv_buf;temp->epfd=epfd;temp->conn_fd=events[i].data.fd;//printf("进入线程\n");pthread_detach(pth1);pth1=pthread_create(&pth1,NULL,solve,temp);//开一个线程去判断任务类型从而执行 值传递//solve((void*)temp);} }}close(sock_fd);//还要关闭其他打开的套接字 别忘了
}
因为在其中存储消息时使用了邻接表,下标对应除了map没有想到更好的方法,为了使用特性就使用了cpp,,
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"Data.h"
#include
#include
#include
#include
#include
#include
#include
using namespace std;
char gl_account[MAX_ACCOUNT]; //以一个全局变量记录账号
int fact_fd; //记录服务器套接字
char fact_name[MAX_USERNAME];
int tt=1; //更新映射表主键
int dd=1; //更新群与好友和消息的映射关系map<int,int>mp; //主键为账号 键值为头指针
map<int,int>mp_group;//群的映射表list_friend_t head;//存储一次登录的好友信息list_messages_t Messages[1024]; //与每一个好友的消息链表list_messages_t Message_BOX;//消息盒子链表list_group_t group_head; //群链表 //每一个群分配一个唯一主键 list_member_t member[1024];//每一个群中的群员链表list_group_messages_t group_messages[1024];//每一个群中的消息链表int my_recv(int conn_fd,char *data_buf,int len) //这个可以参考 也很巧妙
{static char recv_buf[BUFSIZ]; //8192 static char *phread;static int len_remain = 0;int i;if(len_remain<=0) //能够第二次接着发 保存第一次没发完的数据 {if((len_remain=recv(conn_fd,recv_buf,sizeof(recv_buf),0))<0){perror("recv\n");exit(1);}else if(len_remain==0){return 0;}phread=recv_buf;}for(i=0;*phread!='\n';i++) //防止一次发送没有发送完 所以设置为static {if(i>len) return 0;data_buf[i]=*phread;phread++;len_remain--;}len_remain--; //回车结束符号phread++; //为了与上面进行对应return i;
}//因为这里用了char* 所以所有的地方都要进行强转
int my_recv_tmp(int conn_fd,char *data_buf,int len)
{char *p = data_buf;//printf(" 需要接收 %d\n",len);//memset(data_buf, 0, len);while (len > 0) {ssize_t n = recv(conn_fd, p, len, 0);if (n < 0)perror("error in recv\n");else if (n == 0)printf("接收到包大小为零\n");else {//printf("recv %zd bytes: %s\n", n, p);p += n;len -= n;}}//因为tcp是流式的 一个包可能被分成几个小段来发送//所以必须要用一个循环来接收 保证包的大小//p[len]='\0';return len;
}int get_userinfo(char *buf,int len)
{int i,c;if(buf==NULL)return -1;i=0;while((c=getchar())!='\n' && c!=EOF && i<=len-2){buf[i++]=c;}buf[i++]='\0';return 0;
}int input_userinfo(recv_t *temp)
{int flag_userinfo=0;char password[MAX_RECV];fflush(stdin);printf("account:");if(get_userinfo(gl_account,MAX_ACCOUNT)<0){perror("get_userinfo\n");exit(1);}strcpy(temp->send_Account,gl_account);printf("password:");if(get_userinfo(password,MAX_PASSWORD)<0){perror("get_userinfo\n");exit(1);}strcpy(temp->message,password);
}//接包
//执行这个函数以后为在此文件中存储一个好友信息链表 可以进行各种操作
//计划把数据接收来以后进行存放 以链表形式存储 分页形式显示
//最后有一个标记位为 EOF 的结束包
int FetchAll_for_Friend_List()
{//List_DelNode(head);recv_t pacage;int ans=0;while(1){printf("好友列表 %d\n",sizeof(recv_t));//if(my_recv_tmp(fact_fd,(char*)&pacage,sizeof(recv_t))<0)//perror("error in recv\n");//收包int ret = recv(fact_fd,&pacage,sizeof(pacage),MSG_WAITALL);// pacage=(recv_t)pacage;//printf(":::::收到好友包 %s %d\n",pacage.message,pacage.type);if(pacage.type==EOF_OF_BOX)break;//接收到EOF结束符 退出接收循环//利用数据包中数据对链表结点进行赋值list_friend_t temp=(list_friend_t)malloc(sizeof(node_friend_t));bzero(temp,sizeof(node_friend_t));temp->status=pacage.conn_fd;//状态strcpy(temp->recv_account,pacage.message_tmp);//好友账号strcpy(temp->nickname,pacage.message);//昵称//printf("%s\n",temp->nickname);//printf("%d::%d\n",++ans,temp->status);List_AddTail(head,temp);//建立链表}//getchar();
}int login_client(int conn_fd,char *username)
{map<int,int>pp;List_Init(Message_BOX,node_messages_t); //初始化消息盒子链表 后面只需要添加即可fact_fd=conn_fd; //给全局变量赋值int ret;recv_t Package;Package.type = LOGIN;Package.send_fd= conn_fd;char buf[MAX_RECV];int number=3;getchar();system("clear");while(number--) //三次机会{system("clear");input_userinfo(&Package);if(send(conn_fd,&Package,sizeof(recv_t),0)<0)//发送一个登录请求{perror("error in send\n");return 0; //错误退出}//如果登录请求正确bzero(username,sizeof(username));if((ret=my_recv_tmp(conn_fd,username,MAX_USERNAME))<0) //默认阻塞{perror("error in my_recv\n");exit(1);}//printf("%s::\n",username);if(username[0]==ERROR_IN_LOGIN){printf("account or password error!\n");continue;}else break;}strcpy(fact_name,username);//给全局变量赋值if(number==-1) return 0;//登录成功以后开始接收离线消息盒子里的消息my_recv_tmp(conn_fd,buf,14);Box_t tttt;bzero(&tttt,sizeof(Box_t));if(buf[0]=='@'){printf("消息盒子无记录!\n");//登陆成功 离线消息盒子无记录 进入服务界面//recv(conn_fd,&tttt,sizeof(Box_t),MSG_WAITALL);}else{Box_t box;char buffer[32];//printf("进行到循环\n");while(1){if(my_recv_tmp(conn_fd,(char*)&box,sizeof(box))<0)perror("error in recv\n");if(box.type==EOF_OF_BOX){//printf("%s %d %s \n",box.message,box.type,box.account);break;}//printf("%s:\n%s\n",box.account,box.message); //显示离线发送来的信息if(box.type==ADD_FRIENDS){printf("\033[32m There is a friend request: \033[0m \n");printf("%s:%s\n",box.account,box.message);printf("YES [Y] NO [N]\n");scanf("%s",buffer);getchar();Package.type=ADD_FRIENDS_QUERY;strcpy(Package.message,buffer);//有发送者和接收者就可以确定好友关系 加入数据库strcpy(Package.recv_Acount,box.account);//发送者strcpy(Package.send_Account,gl_account);//接收者 //一种新的事件 epoll来判断if(send(conn_fd,&Package,sizeof(recv_t),0)<0)perror("error in send friend request\n"); //根据首字母判断list_friend_t fri = (list_friend_t)malloc(sizeof(node_friend_t));strcpy(fri->send_account,Package.recv_Acount);List_AddTail(head,fri);}//说明这是一个消息类型的包 服务器会先发 你发送的消息 后发你接收的消息//把这些接收来的包放入账号主键唯一对应的键值}}//下面这行代码是当时调错用的 错误原因为服务器逻辑出现问题//Box_t box;//recv(conn_fd,&box,sizeof(box),0); //处理多接收了一个包//printf("%s %d %s \n",box.message,box.type,box.account);//接收完离线消息盒子记录开始接收好友信息列表//实现为直接开始收包 最后一个结束包其中无数据 标记位为EOFFetchAll_for_Friend_List();//好友关系记录发送完成 结尾为一个结尾包 //printf("好友列表加载完成\n");//接收到一个标记包 表示是否有消息记录printf("是否有消息 %d\n",sizeof(buf));if(my_recv_tmp(conn_fd,buf,14)<0)perror("error in client recv messages record\n");int pause_tmp_for_messages=0;if(buf[0]=='@'){printf("消息记录无记录!\n");pause_tmp_for_messages=1;}else{//若有消息记录开始加载Box_t box;while(1){//printf("开始接收消息 %d\n",sizeof(Box_t));if(my_recv_tmp(conn_fd,(char*)&box,sizeof(Box_t))<0)perror("error in recv\n");
/* printf("%s\n",box.message);getchar(); *///printf("%s %s\n",box.message,box.account);if(box.type==EOF_OF_BOX) //接收到结束包会退出break;if(box.type==SEND_MESSAGES)//顺序为 链表前为老信息 链表后为新信息{if(!mp[atoi(box.account)])//表中值为零说明第一次{mp[atoi(box.account)]=tt++; //对应唯一二维数组的索引
/* printf("%d %s %d\n",strlen(box.account),box.account,tt-1);getchar(); */List_Init(Messages[mp[atoi(box.account)]],node_messages_t);}list_messages_t temp=(list_messages_t)malloc(sizeof(node_messages_t)); //销毁链表strcpy(temp->messages,box.message);strcpy(temp->send_account,box.account);//好友账号strcpy(temp->nickname,box.usename); //username 必为好友本人的temp->type=box.type;List_AddTail(Messages[mp[atoi(box.account)]],temp);//将消息加入链表}}}if(pause_tmp_for_messages){Box_t ttttt;bzero(&ttttt,sizeof(Box_t));recv(conn_fd,&ttttt,sizeof(Box_t),MSG_WAITALL);}//在这里开始接收群消息recv_t tmp;int yyy=0;while(1){ //if(my_recv_tmp(conn_fd,(char*)&tmp,sizeof(recv_t))<0)bzero(&tmp,sizeof(recv_t)); //接收到的消息要加 入两个链表tmp.type = 1000;if(recv(conn_fd,&tmp,sizeof(recv_t),MSG_WAITALL)<0)perror("error in recv\n");printf("标记位 sd%s %d \n",tmp.message,tmp.type);if(tmp.type==EOF_OF_BOX)break;if(tmp.type==NULL_OF_GROUP){yyy=1;break;}list_group_t cur=(list_group_t)malloc(sizeof(node_group_t));strcpy(cur->account,tmp.message);//群号strcpy(cur->nickname,tmp.message_tmp);//群昵称List_AddTail(group_head,cur);//加到群链表}if(!yyy) //这个代码写的很丑陋 //给每一个群添加消息信息 //这个地方其实设计的有问题 会将数据库所有的消息都发来 //这个接收包的过程可以对群消息和群成员的链表进行填充while(1){bzero(&tmp,sizeof(recv_t)); //清空结构体 防止隐性错误if(my_recv_tmp(conn_fd,(char*)&tmp,sizeof(recv_t))<0)perror("error in recv\n");if(tmp.type==EOF_OF_BOX)break; //接收到结束包if(mp_group[atoi(tmp.recv_Acount)]==0){mp_group[atoi(tmp.recv_Acount)]=tt++;//分配唯一的映射关系List_Init(group_messages[mp_group[atoi(tmp.recv_Acount)]],node_group_messages_t)List_Init(member[mp_group[atoi(tmp.recv_Acount)]],node_member_t);}//分配群消息链表list_group_messages_t cur=(list_group_messages_t)malloc(sizeof(node_group_messages_t));bzero(cur,sizeof(node_group_messages_t));strcpy(cur->account,tmp.send_Account);//好友账号strcpy(cur->messages,tmp.message_tmp);//消息cur->type=tmp.type; //群成员的等级//printf("消息:%s\n",cur->messages);List_AddTail(group_messages[mp_group[atoi(tmp.recv_Acount)]],cur);//分配群成员链表//printf(" 成员 %s\n",tmp.send_Account);/* if(pp[atoi(tmp.send_Account)]==0) //有bug 一个成员只能在所有群中加载一次{pp[atoi(tmp.send_Account)]++;printf(" 成员 :::%s\n",tmp.recv_Acount);list_member_t cdr=(list_member_t)malloc(sizeof(node_member_t));strcpy(cdr->account,tmp.send_Account);//其他消息并没有//printf("成员 %s\n",cdr->account);List_AddTail(member[mp_group[atoi(tmp.recv_Acount)]],cdr);} *///保证每一个群每个号只出现一次list_member_t cdr=(list_member_t)malloc(sizeof(node_member_t));int lzl=0;//printf("群账号 %s\n",tmp.recv_Acount);List_ForEach(member[mp_group[atoi(tmp.recv_Acount)]],cdr){if(!strcmp(cdr->account,tmp.send_Account)){lzl=1;break;}}//原因在于cdr有malloc后分配的值 所以其值其实始终相当于消息链表的头结点 所以插入后一直没有值//其实最简单的就是不给cur分配内存空间//free(cdr); //这里free会把原链表freeif(!lzl){list_member_t cudr=(list_member_t)malloc(sizeof(node_member_t));//这里插入不可以使用上面的cdr 刚开始没写free时也不可以 链表会乱strcpy(cudr->account,tmp.send_Account);//其他消息并没有List_AddTail(member[mp_group[atoi(tmp.recv_Acount)]],cudr);}}return 1; //登陆成功 进入服务界面
}int register_client(int conn_fd,char *account) //注册请求 返回一个账号
{int ret;recv_t Package;Package.type = REGISTER;Package.send_fd= conn_fd;char password[MAX_PASSWORD];char telephone[MAX_TELEPHONE];char nickname[MAX_USERNAME];system("clear");//收集注册信息printf("Welcome to register account!\n");printf("please enter your password,we will give your a unique account.\n");printf("password:");getchar();get_userinfo(password,MAX_PASSWORD);printf("Get it back for your password\n");printf("telephone:");get_userinfo(telephone,MAX_TELEPHONE);printf("please enter you nickname!\n");get_userinfo(nickname,MAX_USERNAME);strcpy(Package.recv_Acount,nickname);strcpy(Package.message_tmp,telephone);strcpy(Package.message,password);if((ret=send(conn_fd,&Package,sizeof(recv_t),0))<0){perror("error in register send\n");return 0;}if((ret=my_recv_tmp(conn_fd,account,MAX_ACCOUNT))<0){perror("error in register send\n");return 0;}if(account[0]==ERROR_IN_LOGIN){perror("error in server data\n");return 0;}printf("This is your Account ,Don't forget it!\n");printf("Account:%s\nPlease enter again!\n",account);printf("please enter enter key for quit!\n");getchar();return 1; //发送正确且收到账号消息返回1
}int Retrieve_client(int conn_fd)
{//变量声明int ret=0;recv_t Package;Package.type = RETRIEVE;Package.send_fd= conn_fd;char Account[MAX_ACCOUNT];char telephone[MAX_TELEPHONE];char new_password[MAX_PASSWORD];char flag[32]; //仅用作收一次数据system("clear");//数据获取getchar();printf("Please enter your Account:");get_userinfo(Account,MAX_ACCOUNT);printf("Please enter your telephone:");get_userinfo(telephone,MAX_TELEPHONE);printf("enter your new password:");get_userinfo(new_password,MAX_PASSWORD);strcpy(Package.send_Account,Account);strcpy(Package.message_tmp,telephone);strcpy(Package.message,new_password);//发包if((ret=send(conn_fd,&Package,sizeof(recv_t),0))<0){perror("error in retrieve send\n");return 0;}if((ret=my_recv_tmp(conn_fd,flag,32))<0){perror("error in retrieve send\n");return 0;}if(flag[0]=='@') //正确的话发送一个"y“{printf("Password change failed\n");return 0;}printf("Password changed successfully\n");return 1;
}int Add_Friend(int conn_fd)
{int ret=0;recv_t Package;Package.type = ADD_FRIENDS;Package.send_fd= conn_fd;char Account[MAX_ACCOUNT];char message[MAX_RECV]; //添加好友时给对方发送的话char temp[64]; //就是一个接收消息的缓冲区system("clear");getchar();printf("Please enter a Account you want to add:");get_userinfo(Account,MAX_ACCOUNT);printf("please enter your friendly greeting:\n");get_userinfo(message,MAX_RECV);strcpy(Package.recv_Acount,Account);strcpy(Package.message,message);strcpy(Package.send_Account,gl_account); //全局变量if((ret=send(conn_fd,&Package,sizeof(recv_t),0))<0){perror("error in add friend send\n");return 0;}printf("The message has been sent,please wait for it to be accepted\n");getchar();return 1;
}int Del_Friend(int conn_fd)
{int ret=0;recv_t Package;Package.type = DEL_FRIENDS;Package.send_fd= conn_fd;char Account[MAX_ACCOUNT];list_friend_t curpos;char message[MAX_RECV]; //添加好友时给对方发送的话char temp[64]; //就是一个接收消息的缓冲区system("clear");getchar();printf("Please enter a Account you want to delete:");get_userinfo(Account,MAX_ACCOUNT);printf("Do you sure delete %s?[Y]yes / [N]no\n",Account);scanf("%s",temp);if(!(temp[0]=='Y'||temp[0]=='y'?1:0)) return 0;strcpy(Package.recv_Acount,Account);strcpy(Package.send_Account,gl_account); //全局变量 代表本身的账号if((ret=send(conn_fd,&Package,sizeof(recv_t),0))<0){perror("error in del friend send\n");return 0;}List_ForEach(head,curpos){if(!strcmp(Account,curpos->recv_account));{List_DelNode(curpos);break;}}printf("%s have been delete!\n"); //没检测是否存在getchar();return 1;
}int send_friend_messages(char *account,char *Message)
{recv_t package;package.type=SEND_MESSAGES;strcpy(package.message,Message); //消息strcpy(package.recv_Acount,account); //消息接收者strcpy(package.send_Account,gl_account); //消息发送者if(send(fact_fd,&package,sizeof(recv_t),0)<0)perror("error in send friend messages\n"); //相当于只发不收 收在一个专门收的函数中return 0;
}//这个函数的意义是打开一个窗口 你可以与你输入的账号的好友聊天
//除非点击输入 否则每隔0.5秒刷新一次页面 防止有新的信息收到而无法显示
//单独开的那个线程把所有收到的消息放到一个链表中 对于好友请求没有读就不会从数据库中删除
//每次刷新页面时在这个链表中搜索 有新的消息就打印出来
//服务器如何存储消息信息 与好友请求同存储于一张表中 在每次登录时发送 对于每一个好友建立一个以其
//账号为主键的map 键值为指针 指向一个链表 //在每次搜寻和发送时都在链表中中加入消息 同时服务器进行存储 int Chat(char *account)//参数为好友账号
{char Message[MAX_RECV];Pagination_t paging;node_messages_t *pos;char acc_tmp[MAX_ACCOUNT];strcpy(acc_tmp,account);int i;char choice;int flag=0;list_messages_t curos;paging.totalRecords=0;if(Messages[mp[atoi(account)]]==NULL) //说明没有消息 链表还未初始化{printf("初始化\n");mp[atoi(account)]=++tt;List_Init(Messages[mp[atoi(account)]],node_messages_t);}List_ForEach(Messages[mp[atoi(account)]],curos) paging.totalRecords++;//遍历消息链表paging.offset = paging.totalRecords;paging.pageSize = MESSADES_PAGE_SIZE;Paging_Locate_FirstPage(Messages[mp[atoi(account)]], paging);while(1){system("clear");//printf("链表长度:%d\n",paging.totalRecords);//在消息盒子中查找是否有正在发消息的好友发送来的消息List_ForEach(Message_BOX,curos) {//消息肯定是发送者是正在聊天的好友的账号if(curos->type==SEND_MESSAGES && !strcmp(curos->send_account,account)){list_messages_t temp = (list_messages_t)malloc(sizeof(node_messages_t));strcpy(temp->messages,curos->messages);strcpy(temp->recv_account,curos->recv_account);strcpy(temp->nickname,curos->nickname);List_AddTail(Messages[mp[atoi(account)]],temp);paging.totalRecords+=1;//更新消息链表List_FreeNode(curos); //这个消息已经载入消息链表 可以删除了}}
/* List_ForEach(Messages[mp[atoi(account)]],curos){cout << curos->messages << endl;} */printf("\001\033[1m\002");printf("\033[34m");printf("\n==================================================================\n");printf("**************************** %s ****************************\n",account);//有消息可以用这个 Messages[mp[account]]->nickname//没有消息不就凉了printf("------------------------------------------------------------------\n");//printf("((((%d,%s,%d\n",mp[atoi(acc_tmp)],acc_tmp,strlen(acc_tmp));printf("\001\033[0m\002");Paging_ViewPage_ForEach(Messages[mp[atoi(acc_tmp)]], paging, node_messages_t, pos, i){//链表中名称必为好友昵称//printf("%s :%s :\n",pos->nickname,fact_name);if(strcmp(pos->nickname,fact_name))//怎么比都可以{printf("\033[32m %-65s \033[0m \n",pos->nickname);printf("\033[32m %-65s \033[0m \n",pos->messages);}else{printf("\033[35m %65s \033[0m \n",fact_name);printf("\033[35m %65s \033[0m \n",pos->messages);}putchar('\n');}printf("\001\033[1m\002");printf("\033[34m");printf("------- Total Records:%2d ----------------------- Page %2d/%2d ----\n",paging.totalRecords, Pageing_CurPage(paging),Pageing_TotalPages(paging));printf("******************************************************************\n");printf("[P]revPage | [N]extPage | [I]uput | [R]eturn");printf("\n==================================================================\n");printf("Your Choice:");printf("\001\033[0m\002");fflush(stdin);scanf("%c", &choice);getchar();fflush(stdin);switch (choice) {case 'I':case 'i':{//将消息在发向服务器的同时把消息存入 本好友账号映射的消息链表printf("please enter:\n");cin.getline(Message,sizeof(Message)); //发送者 接收者 消息 昵称不急list_messages_t temp=(list_messages_t)malloc(sizeof(node_messages_t));strcpy(temp->messages,Message);strcpy(temp->send_account,gl_account);strcpy(temp->nickname,fact_name);//随便即可 只要不和自己的名字重合strcpy(temp->recv_account,account);
/* if(mp[account]==0)mp[account]=++tt; */List_AddTail(Messages[mp[atoi(account)]],temp);//加入到消息链表paging.totalRecords++;send_friend_messages(account,Message);}break;case 'p':case 'P':if (!Pageing_IsFirstPage(paging)) {Paging_Locate_OffsetPage(Messages[mp[atoi(acc_tmp)]], paging, -1, node_messages_t);}break;case 'n':case 'N':if (!Pageing_IsLastPage(paging)) {Paging_Locate_OffsetPage(Messages[mp[atoi(acc_tmp)]], paging, 1, node_messages_t);}break;case 'r':case 'R':flag=1;break;}if(flag) break;}
}int show_friend_list()//套接字为全局变量
{char account[MAX_ACCOUNT];Pagination_t paging;node_friend_t *pos;int i;char choice;list_friend_t curos;paging.totalRecords=0;List_ForEach(head,curos) paging.totalRecords++;paging.offset = 0;paging.pageSize = FRIEND_PAGE_SIZE;//paging.totalRecords = FetchAll_for_Friend_List(head);Paging_Locate_FirstPage(head, paging);do {system("clear");//printf("链表长度:%d\n",paging.totalRecords);printf("\n==============================================================\n");printf("********************** Friend List **********************\n");printf("%10s %20s %15s\n", "account", "Name","status");printf("------------------------------------------------------------------\n");Paging_ViewPage_ForEach(head, paging, node_friend_t, pos, i){printf("%10s %20s %15d \n",pos->recv_account,pos->nickname,pos->status);}printf("------- Total Records:%2d ----------------------- Page %2d/%2d ----\n",paging.totalRecords, Pageing_CurPage(paging),Pageing_TotalPages(paging));printf("******************************************************************\n");printf("[P]revPage | [N]extPage | [Q]uery | [R]eturn");printf("\n==================================================================\n");printf("Your Choice:");fflush(stdin);scanf("%c", &choice);fflush(stdin);switch (choice) {case 'q':case 'Q':printf("Please enter an account you want to chat with:\n");scanf("%s",account);getchar();Chat(account);break;case 'p':case 'P':if (!Pageing_IsFirstPage(paging)) {Paging_Locate_OffsetPage(head, paging, -1, node_friend_t);}break;case 'n':case 'N':if (!Pageing_IsLastPage(paging)) {Paging_Locate_OffsetPage(head, paging, 1, node_friend_t);}break;}} while (choice != 'r' && choice != 'R'); //链表在客户端退出时进行销毁
}//注册成功后要加入群列表
//注册请求 返回一个唯一的群号
//和登录账号用一个 都是唯一的
int register_group_client(int conn_fd)
{int ret;recv_t Package;Package.type = REGISTER_GROUP;Package.send_fd= conn_fd; //这个人要被标记为群主char nickname[MAX_USERNAME];char account[MAX_ACCOUNT];system("clear");//收集注册信息printf("Welcome to register group!\n");printf("please enter your group nickname,we will give your a unique account.\n");printf("please enter you nickname!\n");getchar();get_userinfo(nickname,MAX_USERNAME);strcpy(Package.message,nickname);strcpy(Package.send_Account,gl_account);if((ret=send(conn_fd,&Package,sizeof(recv_t),0))<0){perror("error in register send\n");return 0;}
/* if((ret=my_recv(conn_fd,account,MAX_ACCOUNT))<0) //接收账号 在服务器存入数据库{perror("error in register send\n");return 0;} */usleep(500);//等待下消息到来list_messages_t tttt;List_ForEach(Message_BOX,tttt){if(tttt->type==REGISTER_GROUP){strcpy(account,tttt->send_account);//参数为提前定制printf("消息盒子中找到 %s %s\n",account,tttt->send_account);List_DelNode(tttt); //从消息盒子中获取break;}}if(account[0]==ERROR_IN_LOGIN)//这个标记位可以标记所有的错误事件{perror("error in server data\n");return 0;}printf("This is your Account ,Don't forget it!\n");printf("Account:%s\n",account);printf("please enter enter key for quit!\n");getchar();list_group_t tmp=(list_group_t)malloc(sizeof(node_group_t));strcpy(tmp->nickname,nickname);strcpy(tmp->account,account);List_AddTail(group_head,tmp);//加入到群链表mp[atoi(account)]=dd++; //分配唯一主键List_Init(member[mp[atoi(account)]],node_member_t);//初始化这个群的成员链表list_member_t temp=(list_member_t)malloc(sizeof(node_member_t));strcpy(temp->account,gl_account);strcpy(temp->nickname,fact_name);temp->type=OWNER; //申请者为群主List_AddTail(member[mp[atoi(account)]],temp); //加入一个新的成员return 1; //发送正确且收到账号消息返回1
}int Add_group(int conn_fd)
{int ret=0;recv_t Package;Package.type = ADD_GROUP;Package.send_fd= conn_fd;strcpy(Package.recv_Acount,gl_account);strcpy(Package.message_tmp,fact_name);char Account[MAX_ACCOUNT];char message[MAX_RECV]; //添加好友时给对方发送的话char temp[64]; //就是一个接收消息的缓冲区char buf[MAX_USERNAME];system("clear");getchar();printf("Please enter a Group_Account you want to add:");get_userinfo(Account,MAX_ACCOUNT);strcpy(Package.message,Account); //要加入的账号if((ret=send(conn_fd,&Package,sizeof(recv_t),0))<0){perror("error in add friend send\n");return 0;}else printf("You have joined this group:%s\n",Account);getchar();/* if(recv(conn_fd,buf,sizeof(buf),0)<0) //返回一个名称perror("error in recv\n"); */list_messages_t tttt;List_ForEach(Message_BOX,tttt){if(tttt->type==REGISTER_GROUP){strcpy(buf,tttt->messages);//参数为提前定制List_DelNode(tttt); //从消息盒子中获取break;}}list_group_t tmp=(list_group_t)malloc(sizeof(node_group_t));strcpy(tmp->account,Account);strcpy(tmp->nickname,buf);List_AddTail(group_head,tmp);//相当于当先用户加入一个新的群//登录阶段加载所有消息记录 以便加入新群后有消息 其实可以加入后再从服务器获取//这样效率肯定会更高//项目时间快到了只能先赶进度了if(member[mp_group[atoi(Account)]]==NULL)List_Init(member[mp_group[atoi(Account)]],node_member_t);list_member_t tmmp=(list_member_t)malloc(sizeof(node_member_t));strcpy(tmmp->account,gl_account);tmmp->type=COMMON;List_AddTail(member[mp_group[atoi(Account)]],tmmp); //成员链表中加入if(group_messages[mp_group[atoi(Account)]]==NULL)List_Init(group_messages[mp_group[atoi(Account)]],node_group_messages_t);list_group_messages_t tmmmp=(list_group_messages_t)malloc(sizeof(node_group_messages_t));strcpy(tmmmp->messages,"hello everyone!");strcpy(tmmmp->account,gl_account);//好友账号tmmmp->type=COMMON;List_AddTail(group_messages[mp_group[atoi(Account)]],tmmmp);return 1;
}int Quit_group(int conn_fd)
{int ret=0;recv_t Package;Package.type = QUIT;Package.send_fd= conn_fd;strcpy(Package.recv_Acount,gl_account);//本身的账号char Account[MAX_ACCOUNT];char message[MAX_RECV]; //添加好友时给对方发送的话char temp[64]; //就是一个接收消息的缓冲区char buf[MAX_USERNAME];system("clear");getchar();printf("Please enter a Group_Account you want to quit:\n");get_userinfo(Account,MAX_ACCOUNT);strcpy(Package.message,Account); //要删除的账号if((ret=send(conn_fd,&Package,sizeof(recv_t),0))<0){perror("error in add friend send\n");return 0;}else printf("You have quited this group:%s\n",Account);getchar();list_group_t tmp;List_ForEach(group_head,tmp) //从组群中删除这个群{if(!strcmp(tmp->account,Account)){List_DelNode(tmp);break;}}return 1;
}int Dissolve(int conn_fd)
{char buf[256];list_member_t curps;list_group_t curpss;char account[MAX_ACCOUNT];recv_t packet;packet.type=DISSOLVE;printf("please enter the group you want to dismiss\n");scanf("%s",account);getchar();strcpy(packet.recv_Acount,account);//要解散的群号if(send(conn_fd,&packet,sizeof(packet),0)<0)perror("error in send dissolve!\n");//服务器应该在两个表中分别删除//客户端应该在三个链表中分别删除
/* List_ForEach(member[mp_group[atoi(account)]],curps){if(!strcpy(curps->account,gl_account)){if(curps->type==OWNER){List_DelNode(curps);//在本地删除链表中的数据}}} */if(member[mp_group[atoi(account)]]!=NULL)List_Destroy(member[mp_group[atoi(account)]],node_member_t);if(group_messages[mp_group[atoi(account)]]!=NULL)List_Destroy(group_messages[mp_group[atoi(account)]],node_group_messages_t);//删除两个链表中的消息printf("到达!\n");//bzero(curpss,sizeof(node_group_t));List_ForEach(group_head,curpss){printf("%s %s\n",curpss->account,account);if(!strcmp(curpss->account,account)){printf("找到\n");List_DelNode(curpss); //群成员链表中删除break;}}}//printf("Lack of permissions\n");//该函数用在查看群列表以后 可以将某人设为管理员
int Set_Admin(int conn_fd,char * count) //后一个参数为当前群号
{list_member_t curps;char account[MAX_ACCOUNT];recv_t package;memset(&package,0,sizeof(package));int flag=0;List_ForEach(member[mp_group[atoi(count)]],curps){if(!strcmp(gl_account,curps->account) && curps->type==OWNER){flag=1;break;}}if(!flag){printf("Lack of permissions!\n");return 0;}else{package.type=SET_ADMIN;printf("please enter who you what to talk about as admin:\n");scanf("%s",account);getchar();strcpy(package.message,account); //成为管理员的账号strcpy(package.message_tmp,count); //群号if(send(conn_fd,&package,sizeof(recv_t),0)<0)//在服务器修改这个账号的状态perror("error in send!\n");List_ForEach(member[mp_group[atoi(count)]],curps){if(!strcmp(account,curps->account)){curps->type=ADMIN;//在本地设置为管理员break;}}}
}//上下这两个函数是在进入群员列表的时候的选项int Kicking(int conn_fd,char *count)//第二个参数为群号
{char buf;list_member_t curps;recv_t package;package.type=KICKING;char account[MAX_ACCOUNT];int flag=0;printf("please enter a people count you want to kicking\n");scanf("%s",account);getchar();
/* List_ForEach(member[mp_group[atoi(count)]],curps){if(!strcmp(gl_account,curps->account) && curps->type==OWNER){flag=1;break;}}if(!flag){ printf("You don't have enough permissions!\n");getchar();return 0;} */list_member_t cuurps;List_ForEach(member[mp_group[atoi(count)]],cuurps){if(!strcmp(cuurps->account,account)){List_DelNode(cuurps); //本地群列表中删除break;}}strcpy(package.message,account);//要踢出的人strcpy(package.message_tmp,count);//群号if(send(conn_fd,&package,sizeof(recv_t),0)<0)perror("error in send kicking\n");return 0;
}int recv_file(recv_t *package) //在消息盒子中接收到包 把数据写入文件
{int fd=0; //这个参数是为了说明新文件的权限char buf[MAX_PATH_NAME];bzero(buf,sizeof(buf));//若果输入路径 简单的前面加前缀就不可以strcat(buf,"tmp");strcat(buf,package->message_tmp);//printf("保存在这里 -> %s\n",buf);if((fd=open(buf,O_CREAT|O_RDWR|O_APPEND,S_IRWXU))==-1) //第三个参数的意思就是可读可写可执行//把接收到的文件存入当前目录下perror("Error opening file!");if(write(fd,package->message,strlen(package->message))==-1){perror("Error writing file!");return -1;}close(fd);return 0;
}//可以把在登录以后收到的信息包装成一个消息盒子
//好友请求 好友消息 群聊消息
//在其中处理所有的请求 建立一个消息盒子链表 存储所有数据包 根据其中标记位来辨别请求类型
void *method_client(void *arg)
{recv_t buf;while(1){bzero(&buf,sizeof(recv_t));if(my_recv_tmp(fact_fd,(char*)&buf,sizeof(recv_t))<0)perror("error in recv\n");if(buf.type==RECV_FILE){if(recv_file(&buf)<0){printf("error in recv file\n");}continue;}printf("消息盒子收到消息 %d %s\n",buf.type,buf.send_Account);list_messages_t temp = (list_messages_t)malloc(sizeof(node_messages_t));temp->type=buf.type;//标记位 strcpy(temp->send_account,buf.send_Account);//发送者strcpy(temp->messages,buf.message);//消息strcpy(temp->nickname,buf.message_tmp);//昵称strcpy(temp->recv_account,buf.recv_Acount);//就是本人账号List_AddTail(Message_BOX,temp);}
}int show_group_member(char *account)
{//char account[MAX_ACCOUNT];Pagination_t paging;node_member_t *pos;int i;char choice;list_member_t curos;//printf("成员 %s map : %d\n",account,mp_group[atoi(account)]);paging.totalRecords=0;printf("遍历链表开始\n");if(member[mp_group[atoi(account)]]==NULL)List_Init(member[mp_group[atoi(account)]],node_member_t);List_ForEach(member[mp_group[atoi(account)]],curos) paging.totalRecords++;printf("遍历链表结束\n");paging.offset = 0;paging.pageSize = FRIEND_PAGE_SIZE;//group_head 在登录时已经初始化//paging.totalRecords = FetchAll_for_Friend_List(head);do {Paging_Locate_FirstPage(member[mp_group[atoi(account)]], paging);system("clear");//printf("链表长度:%d\n",paging.totalRecords);printf("\001\033[1m\002");printf("\033[34m");printf("\n==============================================================\n");printf("********************** Group Member List **********************\n");printf("%10s \n", "Group_account");printf("------------------------------------------------------------------\n");Paging_ViewPage_ForEach(member[mp_group[atoi(account)]], paging, node_member_t, pos, i){printf("%10s\n",pos->account);}printf("------- Total Records:%2d ----------------------- Page %2d/%2d ----\n",paging.totalRecords, Pageing_CurPage(paging),Pageing_TotalPages(paging));printf("******************************************************************\n");printf("[C]hat | [K]ick |[S]et_admin\n");printf("[P]revPage | [N]extPage | [R]eturn");printf("\n==================================================================\n");printf("Your Choice:");printf("\001\033[0m\002");fflush(stdin);scanf("%c", &choice);getchar();switch (choice) {case 'c':case 'C':printf("这个功能不一定能用\n");printf("Please enter an account you want to chat with:\n");scanf("%s",account);getchar();Chat(account); //通过群进行聊天break;case 'k':case 'K':paging.totalRecords--;Kicking(fact_fd,account); //踢人break;case 's':case 'S':printf("please enter who you what to set him to admin\n");scanf("%s",account);getchar();Set_Admin(fact_fd,account); //把某人设置为管理员break;case 'p':case 'P':if (!Pageing_IsFirstPage(paging)) {Paging_Locate_OffsetPage(member[mp_group[atoi(account)]], paging, -1, node_member_t);}break;case 'n':case 'N':if (!Pageing_IsLastPage(paging)) {Paging_Locate_OffsetPage(member[mp_group[atoi(account)]], paging, 1, node_member_t);}break;}} while (choice != 'r' && choice != 'R');
}int send_group_messages(char *account,char *Message)//第一个参数为群号 第二个参数为消息
{recv_t packge;bzero(&packge,sizeof(packge));packge.type=SEND_GROUP_MESSAGES;strcpy(packge.message,Message);//消息strcpy(packge.recv_Acount,account);//群号strcpy(packge.send_Account,gl_account);//发送者strcpy(packge.message_tmp,fact_name);//昵称if(send(fact_fd,&packge,sizeof(recv_t),0)<0)perror("error in seng group messages\n");//接下来把消息存入本地邻接表//服务器把消息存入数据库
/* list_group_messages_t tmp=(list_group_messages_t)malloc(sizeof(node_group_messages_t));strcpy(tmp->messages,Message);strcpy(tmp->nickname,fact_name);List_AddTail(group_messages[mp_group[atoi(account)]],tmp); */return 0;
}//这个函数的作用是进行群聊天
int Group_Chat(char *account)//参数为想参与群聊的群号
{char Message[MAX_RECV];Pagination_t paging;node_group_messages_t *pos;char acc_tmp[MAX_ACCOUNT];strcpy(acc_tmp,account);int i;char choice;int flag=0;list_messages_t curos; //用于遍历消息盒子list_group_messages_t cur; //用于遍历本群消息总数paging.totalRecords=0;if(group_messages[mp_group[atoi(account)]]==NULL) //防止一个新注册的群 消息链表还未初始化{printf("初始化\n");mp_group[atoi(account)]=++dd;List_Init(group_messages[mp_group[atoi(account)]],node_group_messages_t);}List_ForEach(group_messages[mp_group[atoi(account)]],cur) paging.totalRecords++;//遍历消息链表paging.offset = paging.totalRecords;paging.pageSize = MESSADES_PAGE_SIZE;Paging_Locate_FirstPage(group_messages[mp_group[atoi(account)]], paging);while(1){flag=0;//退出此函数标记system("clear");//printf("链表长度:%d\n",paging.totalRecords);//在消息盒子中查找是否有正在发消息的好友发送来的消息List_ForEach(Message_BOX,curos) {//消息肯定是发送者是正在聊天的好友的账号 //这个参数为群号if(curos->type==SEND_GROUP_MESSAGES && !strcmp(curos->send_account,account)){list_group_messages_t temp = (list_group_messages_t)malloc(sizeof(node_group_messages_t));strcpy(temp->messages,curos->messages);strcpy(temp->nickname,curos->nickname);List_AddTail(group_messages[mp_group[atoi(account)]],temp);paging.totalRecords+=1;//更新消息链表List_FreeNode(curos); //这个消息已经载入消息链表 可以删除了}}
/* List_ForEach(group_messages[mp_group[atoi(account)]],cur){cout << cur->messages << endl;} */printf("\001\033[1m\002");printf("\033[34m");printf("\n==================================================================\n");printf("**************************** %s ****************************\n",account);//有消息可以用这个 Messages[mp[account]]->nicknamprintf("------------------------------------------------------------------\n");printf("\001\033[0m\002");//printf("((((%d,%s,%d\n",mp[atoi(acc_tmp)],acc_tmp,strlen(acc_tmp));Paging_ViewPage_ForEach(group_messages[mp_group[atoi(account)]], paging, node_group_messages_t, pos, i){//链表中名称必为好友昵称//printf("%s :%s :\n",pos->nickname,fact_name);if(strcmp(pos->nickname,fact_name))//怎么比都可以{printf("\033[32m %-65s \033[0m \n",pos->nickname);printf("\033[32m %-65s \033[0m \n",pos->messages);}else{printf("\033[35m %65s \033[0m \n",fact_name);printf("\033[35m %65s \033[0m \n",pos->messages);}putchar('\n');}printf("\001\033[1m\002");printf("\033[34m");printf("------- Total Records:%2d ----------------------- Page %2d/%2d ----\n",paging.totalRecords, Pageing_CurPage(paging),Pageing_TotalPages(paging));printf("******************************************************************\n");printf("[P]revPage | [N]extPage | [I]uput | [R]eturn");printf("\n==================================================================\n");printf("Your Choice:");fflush(stdin);printf("\001\033[0m\002");scanf("%c", &choice);getchar();fflush(stdin);switch (choice) {case 'I':case 'i':{//这里需要修改 printf("please enter:\n");cin.getline(Message,sizeof(Message)); list_group_messages_t temp=(list_group_messages_t)malloc(sizeof(node_group_messages_t));strcpy(temp->messages,Message);List_AddTail(group_messages[mp_group[atoi(account)]],temp);//加入到消息链表paging.totalRecords++;send_group_messages(account,Message);}break;case 'p':case 'P':if (!Pageing_IsFirstPage(paging)) {Paging_Locate_OffsetPage(group_messages[mp_group[atoi(account)]], paging, -1, node_group_messages_t);}break;case 'n':case 'N':if (!Pageing_IsLastPage(paging)) {Paging_Locate_OffsetPage(group_messages[mp_group[atoi(account)]], paging, 1, node_group_messages_t);}break;case 'r':case 'R':flag=1;break;}if(flag) break;}
}int show_group_list()
{char account[MAX_ACCOUNT];Pagination_t paging;node_group_t *pos;int i;char choice;list_group_t curos;paging.totalRecords=0;List_ForEach(group_head,curos) paging.totalRecords++;paging.offset = 0;paging.pageSize = FRIEND_PAGE_SIZE;//group_head 在登录时已经初始化//paging.totalRecords = FetchAll_for_Friend_List(head);Paging_Locate_FirstPage(group_head, paging);do {//paging.totalRecords=0;//List_ForEach(group_head,curos) paging.totalRecords++;system("clear");//printf("链表长度:%d\n",paging.totalRecords);printf("\n==============================================================\n");printf("********************** Group List **********************\n");printf("%15s %20s\n", "account", "Group_Name");printf("------------------------------------------------------------------\n");Paging_ViewPage_ForEach(group_head, paging, node_group_t, pos, i){printf("%10s %20s\n",pos->account,pos->nickname);}printf("------- Total Records:%2d ----------------------- Page %2d/%2d ----\n",paging.totalRecords, Pageing_CurPage(paging),Pageing_TotalPages(paging));printf("******************************************************************\n");printf("[Q]uery | [C]hat | [D]issolve \n"); printf("[E]xit_from_group\n"); printf("[P]revPage | [N]extPage | [R]eturn\n");printf("\n==================================================================\n");printf("Your Choice:");fflush(stdin);scanf("%c", &choice);//getchar();switch (choice) {case 'c':case 'C':printf("Please enter an account you want to chat with:\n");scanf("%s",account);Group_Chat(account);break;case 'Q':case 'q'://查看群成员列表printf("Please enter an account you want to query with:\n");scanf("%s",account);show_group_member(account);break;case 'd':case 'D':Dissolve(fact_fd); //解散群break;case 'e':case 'E':paging.totalRecords--;Quit_group(fact_fd);//退出群break;case 'p':case 'P':if (!Pageing_IsFirstPage(paging)) {Paging_Locate_OffsetPage(group_head, paging, -1, node_group_t);}break;case 'n':case 'N':if (!Pageing_IsLastPage(paging)) {Paging_Locate_OffsetPage(group_head, paging, 1, node_group_t);}break;}} while (choice != 'r' && choice != 'R');
}void *send_file(void *arg) //发送文件时重新开一个线程
{int fd=0; //文件描述符struct stat buffer;recv_t package;recv_file_t *recv_file=(recv_file_t*)arg;bzero(&buffer,sizeof(struct stat));if(lstat(recv_file->path,&buffer)<0){ //判断文件是否存在printf("File does not exxist!\n");printf("please enter key to quit!\n");getchar();return 0;}printf("%s\n",recv_file->path);if((fd=open(recv_file->path,O_RDONLY))==-1){//确定文件已经存在perror("open file error!\n");printf("please enter key to quit!\n");getchar();return 0;}printf("给%s发送消息\n",recv_file->count);while(read(fd,package.message,MAX_RECV-1)>0) //循环开始发送文件{strcpy(package.message_tmp,recv_file->path);strcpy(package.recv_Acount,recv_file->count);package.type=SEND_FILE;//printf("%s\n",package.message);strcat(package.message,"\0");if(send(fact_fd,&package,sizeof(recv_t),0)<0)perror("error in send file\n");bzero(&package.message,MAX_RECV);//清空缓冲区}bzero(&package,sizeof(recv_t));strcpy(package.recv_Acount,recv_file->count); package.type=EOF_OF_BOX; //发包结束符if(send(fact_fd,&package,sizeof(recv_t),0)<0)perror("error in send file\n");printf("Successfully sent!\n");printf("please enter key to quit!\n");close(fd);return 0;
}
server.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"solve.h" int fffffff;
int main()
{List_Init(status_per,node_status_t);MYSQL mysql; mysql_init(&mysql); //初始化一个句柄 mysql_library_init(0,NULL,NULL);//初始化数据库mysql_real_connect(&mysql,"127.0.0.1","root","123","Login_Data",0,NULL,0);//连接数据库mysql_set_character_set(&mysql,"utf8");//调整为中文字符signal(SIGPIPE,SIG_IGN); //ctrl+c stop int sock_fd,conn_fd;int optval;int flag_recv=USERNAME;int name_num;pid_t pid;socklen_t cli_len;struct sockaddr_in cli_addr,serv_addr;size_t ret;int connect_size=0; //目前连接数 pthread_t pth1;recv_t recv_buf;int epfd,nfds;struct epoll_event ev,events[EVENTS_MAX_SIZE];sock_fd=socket(AF_INET,SOCK_STREAM,0); //服务器套接字if(sock_fd < 0){perror("socket");exit(1);}optval=1; //通用套接字 socket退出后可正常连接 待设置的套接字选项的值及其大小if(setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,(void*)&optval,sizeof(int))<0){perror("setsocket\n");exit(1);}setsockopt(sock_fd,SOL_SOCKET,SO_KEEPALIVE,(void*)&optval,sizeof(int));memset(&serv_addr,0,sizeof(struct sockaddr_in));serv_addr.sin_family=AF_INET; //协议族 ipv4 tcp/ip serv_addr.sin_port=htons(SERV_POT);//服务器端口 serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); //IP地址if(bind(sock_fd,(struct sockaddr*)&serv_addr,sizeof(struct sockaddr_in))<0){perror("bind\n");exit(1);}if(listen(sock_fd,LISTENQ)<0){perror("listen\n");exit(1);}epfd=epoll_create(1);ev.data.fd= sock_fd;ev.events =EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLRDHUP ; //设置为监听读的状态//使用默认的LT模式 // epoll 事件只触发一次epoll_ctl(epfd,EPOLL_CTL_ADD,sock_fd,&ev);connect_size++;for(;;){ nfds = epoll_wait(epfd,events,EVENTS_MAX_SIZE,-1);//等待可写事件for(int i=0;i<nfds;i++){//printf(" The event type is %d\n",events[i].events);connect_size++;if(events[i].data.fd==sock_fd) //服务器套接字接收到一个连接请求{if(connect_size>MAX_CONTECT_SIZE){perror("到达最大连接数!\n");continue;}conn_fd=accept(events[i].data.fd,(struct sokcaddr*)&cli_addr,&cli_len);//网络字节序转换成字符串输出printf("%d accept a new client ! ip:%s\n",++fffffff,inet_ntoa(cli_addr.sin_addr));if(conn_fd<=0){perror("error in accept\n");printf("%s\n",strerror(errno));continue;}ev.data.fd= conn_fd;ev.events =EPOLLIN | EPOLLONESHOT | EPOLLRDHUP;//|EPOLLOUT; //设置事件可写与可写epoll_ctl(epfd,EPOLL_CTL_ADD,conn_fd,&ev); //新增服务器套接字}else if(events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)){//客户端被挂掉 值设置为24 不把事件类型设置为SO_REUSEADDR 会发送很多可读事件//不清楚为什么 有点烦epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,0); close(events[i].data.fd);}else if(events[i].events & EPOLLIN ) //接收到可读 且不是服务器套接字 不用判断 上面已判断 {if((ret=recv(events[i].data.fd,&recv_buf,sizeof(recv_buf),MSG_WAITALL))<0) //接收//包的格式已经提前制定好{perror("recv\n");continue;}if(!ret)//防止客户端异常退出时无法改变状态 客户端异常时会先发送一个大小为零的包{ //对端关闭会发送一个可读事件 但包的大小为一//这个处理限定了一个IP只能登录一个账号list_status_t curps;char buf_tmp[256];List_ForEach(status_per,curps){//printf("一次n");if(curps->fdd==events[i].data.fd){sprintf(buf_tmp,"update Data set status = '0' where Account = '%s'",curps->account);//printf("%s\n",buf_tmp);mysql_query(&mysql,buf_tmp); //改变登录状态List_DelNode(curps); //不正常退出修改状态信息break;}}char buf[128];printf("The client with IP %d is disconnected\n",events[i].data.fd);sprintf(buf,"select *from Data where send_recv_fd = %d",events[i].data.fd);mysql_query(&mysql,buf);MYSQL_RES *result = mysql_store_result(&mysql);MYSQL_ROW row=mysql_fetch_row(result);mysql_free_result(result);sprintf(buf,"update Data set status = \"0\" where send_recv_fd = \"%d\"",events[i].data.fd);mysql_query(&mysql,buf);mysql_free_result(result);continue; }if(recv_buf.type==LOGIN){list_status_t tmp=(list_status_t)malloc(sizeof(node_friend_t));tmp->fdd=events[i].data.fd;//发送者套接字//printf("套接字%d\n",events[i].data.fd);strcpy(tmp->account,recv_buf.send_Account); //连接者账号 //用来在删除时找到账号修改状态List_AddTail(status_per,tmp); //加入在线者链表}recv_buf.send_fd = events[i].data.fd; //发送者的套接字已经改变 应转换为accept后的套接字recv_t *temp=(recv_t*)malloc(sizeof(recv_t)); //防止多线程访问一个结构体*temp=recv_buf;temp->epfd=epfd;temp->conn_fd=events[i].data.fd;//printf("进入线程\n");pthread_detach(pth1);pth1=pthread_create(&pth1,NULL,solve,temp);//开一个线程去判断任务类型从而执行 值传递//solve((void*)temp);} }}close(sock_fd);//还要关闭其他打开的套接字 别忘了
}
不得不说在搭建服务器框架的时候遇到了很多问题 我们集中在下一篇博客中进行分析,服务器的框架在聊天室项目中至关重要,稍不留神就会导致后面代码的重构,那是很让人烦躁的(疯狂艾特)。所以在初始阶段一个正确的框架是我们要下功夫的地方,具体的分析会在下一篇博客中介绍。
solve.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"Data.h"
list_status_t status_per;void send_data(int conn_fd,const char *string,int len) //传入一个连接套接字和字符串数据
{int ree=0;//if((ree=send(conn_fd,string,strlen(string),0))<0)if((ree=send(conn_fd,string,len,0))<0){perror("send");//exit(1);}//printf("返回值%d\n",ree);
}void Delete_for_friend_third(char *a,char *b,char *c) //为了和并出一个唯一的字符串删除好友关系
{if(strcmp(a,b)<=0){strcat(c,a);strcat(c,b);}else{strcat(c,b);strcat(c,a);}
}int login(recv_t *sock,MYSQL *mysql) //sock_fd是要被发送数据的套接字
{int ret;char recv_buf[MAX_USERNAME];//登录时默认使用字符串int flag_recv=USERNAME;char buf[256];sprintf(buf,"select *from Data where Account = %s",sock->send_Account);mysql_query(mysql,buf);MYSQL_RES *result = mysql_store_result(mysql);MYSQL_ROW row=mysql_fetch_row(result);//printf("%s || %s\n",sock->message,row[1]);mysql_free_result(result);MYSQL_RES *res=NULL;int row_in_messages_box=0;char buf_for_error_in_password[MAX_USERNAME];bzero(buf_for_error_in_password,MAX_USERNAME);if(!strcmp(sock->message,row[1]))//在数据库中检测账号密码是否匹配 返回名称 密码在message中{//printf("发送大小 :%d\n",MAX_USERNAME);char send_name[MAX_USERNAME];bzero(send_name,sizeof(send_name));strcpy(send_name,row[3]); //这个问题值得注意 包记得开大send_data(sock->send_fd,send_name,MAX_USERNAME);//发送名称sprintf(buf,"update Data set status = \"1\" where Account = \"%s\"",sock->send_Account);mysql_query(mysql,buf); //改变登录状态//查询消息盒子 把离线期间发送给send_account的消息提取并发送sprintf(buf,"select *from messages_box where recv_account = '%s'",sock->send_Account);mysql_query(mysql,buf);res=mysql_store_result(mysql);if(res==NULL) //等于空就是出现错误 成功不会为NULL 查询的行为0也不会为NULL{perror("error in mysql_store_result\n");return 0;}//先发送一个代表消息盒子是否有信息的包 客户端做出接收 //两种情况分情况编写代码 因为发信息不知道什么时候结束 只能在结束时发送一个代表消息结束的包if((row_in_messages_box=mysql_num_rows(res))==0){//printf("发送大小 :14\n");send_data(sock->send_fd,BOX_NO_MESSAGES,14 * sizeof(char));}else{//printf("发送大小 :14\n");send_data(sock->send_fd,BOX_HAVE_MESSAGS,14 * sizeof(char));}//printf("标志消息盒子 是否有数据的包发送成功 %d\n",row_in_messages_box);//开始发送消息Box_t box;memset(&box,0,sizeof(Box_t));//printf("%d\n",row_in_messages_box);int flag=0;if(row_in_messages_box==0) flag=1;//printf("row_in_messasd %d\n",row_in_messages_box);while(row_in_messages_box--){bzero(&box,sizeof(Box_t));row=mysql_fetch_row(res);box.type=ADD_FRIENDS; //时间类型 离线消息不止添加好友strcpy(box.message,row[3]);//消息strcpy(box.account,row[1]);//发送者//printf("消息盒子 发送大小 :%d\n",sizeof(Box_t));if(send(sock->send_fd,&box,sizeof(Box_t),0)<0)perror("error in send\n");sprintf(buf,"delete from messages_box where recv_account = '%s' and send_acount = '%s' and message = '%s'",sock->send_Account,box.account,box.message);//printf("%s\n",buf);mysql_query(mysql,buf);}if(flag!=1){bzero(&box,sizeof(Box_t));box.type=EOF_OF_BOX;strcpy(box.message,row[3]);//printf("发送大小 :%d\n",sizeof(Box_t));send(sock->send_fd,&box,sizeof(Box_t),0);}//printf("全部信息发送完成\n");}else {strcpy(buf_for_error_in_password,"@@@@@@");send_data(sock->send_fd,buf_for_error_in_password,MAX_USERNAME);//密码账号不匹配 返回错误return 0;}mysql_free_result(res);//发送好友列表的函数所需要的值登录函数中已设置 所以这个数据包可直接使用 //有效位为其中的 send_Account 与 send_fd //谁发的 以及 套接字是多少//printf("函数进行到这里数据库查找数据\n");List_friends_server(sock,mysql);//printf("好友列表加载完成\n");//从消息记录中查找账号所对应的消息记录 标记位为ower_account//根据登陆者的账号在数据库中进行匹配sprintf(buf,"select *from messages_record where ower_account = '%s'",sock->send_Account);mysql_query(mysql,buf);MYSQL_RES *resu=mysql_store_result(mysql);MYSQL_ROW rowwor;//rowwor=mysql_fetch_row(resu);int row_in_messages_record;Box_t box;//根据数据库中有无登录者消息记录 先发送一个起始包 //printf("运行到发送消息标记包\n");if((row_in_messages_record=mysql_num_rows(resu))==0){//printf("消息 发送大小 :%d\n",MAX_RECV);send_data(sock->send_fd,BOX_NO_MESSAGES,14);//return 0; //不退出会在后面多发一个包}else{//printf("消息 发送大小 :%d\n",MAX_RECV);send_data(sock->send_fd,BOX_HAVE_MESSAGS,14);}//printf("row_dddin %d\n",row_in_messages_record);while(row_in_messages_record--){bzero(&box,sizeof(Box_t));rowwor=mysql_fetch_row(resu); //获取消息信息//printf("%s %s\n",sock->send_Account,rowwor[1]);
/* if(!strcmp(sock->send_Account,rowwor[1])) //查找到与之聊天的好友的正确账号 //为查询昵称sprintf(buf,"select *from Data where Account = '%s'",rowwor[2]);else */ sprintf(buf,"select *from Data where Account = '%s'",rowwor[1]);mysql_query(mysql,buf);MYSQL_RES *resu_tmp = mysql_store_result(mysql);MYSQL_ROW rowwor_tmp=mysql_fetch_row(resu_tmp);strcpy(box.message,rowwor[3]); //消息if(!strcmp(rowwor[1],sock->send_Account))strcpy(box.account,rowwor[2]); //必须保证是好友账号else strcpy(box.account,rowwor[1]); //printf("%s\n",box.account);strcpy(box.usename,rowwor_tmp[3]);//发送者昵称//printf("%s\n",rowwor_tmp[3]);box.type=SEND_MESSAGES; //消息类型mysql_free_result(resu_tmp);//printf("消息记录 发送大小 :%d\n",sizeof(Box_t));if(send(sock->send_fd,&box,sizeof(Box_t),0)<0)perror("error in send a message when logging in\n");}bzero(&box,sizeof(Box_t));box.type=EOF_OF_BOX;//printf("消息记录结束包 发送大小 :%d\n",sizeof(Box_t));if(send(sock->send_fd,&box,sizeof(Box_t),0)<0) //发送一个结束包perror("error in send a message when logging in\n");//从这里开始发送群记录和群消息int ddd=0;recv_t tmp_tmp; //客户端用一个while接收bzero(&tmp_tmp,sizeof(recv_t));bzero(buf,sizeof(buf));sprintf(buf,"select *from group_list where member_account = '%s'",sock->send_Account);mysql_query(mysql,buf);MYSQL_RES *res_tmp=mysql_store_result(mysql);MYSQL_ROW rowwo;if((ddd=mysql_num_rows(res_tmp))==0) //群为零 也就不用发送消息记录了{bzero(&tmp_tmp,sizeof(recv_t));tmp_tmp.type=NULL_OF_GROUP;//直接发送标记符//printf("发送标记为\n");strcpy(tmp_tmp.message,"hello worldddddddddd");//printf("没有群 发送大小 :%d %d\n",sizeof(recv_t),tmp_tmp.type);if(send(sock->send_fd,&tmp_tmp,sizeof(recv_t),0)<0) //客户端是一个while循环接收,所以一个足够perror("error in send\n");return 0;}else{//printf("群不为零\n");while(ddd--) //ddd为所有的群的总数{int ret=0;rowwo=mysql_fetch_row(res_tmp);bzero(&tmp_tmp,sizeof(recv_t));strcpy(tmp_tmp.message,rowwo[0]);//所有包含本用户的群strcpy(tmp_tmp.message_tmp,rowwo[2]);//昵称tmp_tmp.type=atoi(rowwo[3]);//在群中的职位//printf("发送群消息\n");//printf("群信息 发送大小 :%d\n",sizeof(recv_t));if((ret=send(sock->send_fd,&tmp_tmp,sizeof(recv_t),0)<0)){perror("error in send group\n");}}bzero(&tmp_tmp,sizeof(recv_t)); //发送一个结束包tmp_tmp.type=EOF_OF_BOX;//printf("结束包已发送\n");//printf("发送大小 :%d\n",sizeof(recv_t));if((ret=send(sock->send_fd,&tmp_tmp,sizeof(recv_t),0)<0)){perror("error in send group\n");}}mysql_free_result(res_tmp);//开始发送消息sprintf(buf,"select *from group_messsges_list",sock->send_Account);mysql_query(mysql,buf);res_tmp=mysql_store_result(mysql);int fff=mysql_num_rows(res_tmp); //获取所有消息 发送给客户端//printf("消息记录共有: %d\n",fff);while(fff--){//在客户端在接收的时候根据bzero(&tmp_tmp,sizeof(recv_t));rowwo=mysql_fetch_row(res_tmp);strcpy(tmp_tmp.message,rowwo[2]);//昵称strcpy(tmp_tmp.message_tmp,rowwo[3]);//消息strcpy(tmp_tmp.recv_Acount,rowwo[0]);//群账号strcpy(tmp_tmp.send_Account,rowwo[1]);//好友账号tmp_tmp.type=atoi(rowwo[4]);//好友的等级 群主 管理员//printf("群消息 发送大小 :%d\n",sizeof(recv_t));if(send(sock->send_fd,&tmp_tmp,sizeof(recv_t),0)<0){perror("error in send\n");}}bzero(&tmp_tmp,sizeof(recv_t)); //发一个结束包tmp_tmp.type=EOF_OF_BOX;//printf("发送大小 :%d\n",sizeof(recv_t));if(send(sock->send_fd,&tmp_tmp,sizeof(recv_t),0)<0){perror("error in send\n");}return 0;
}int register_server(recv_t * sock,MYSQL *mysql)
{char account[MAX_ACCOUNT];char buf[256];memset(account,0,sizeof(account));mysql_query(mysql,"select *from Account");//perror("error in mysql_query\n");MYSQL_RES *result = mysql_store_result(mysql);MYSQL_ROW row=mysql_fetch_row(result);//itoa(row[0]+1,account,10); //atoi字符串转数字//数字转化为字符串必须用sprintf itoa不标准sprintf(account,"%d",atoi(row[0])+1);sprintf(buf,"update Account set Account = \"%s\" where Account = \"%s\"",account,row[0]);mysql_query(mysql,buf);send_data(sock->send_fd,account,MAX_ACCOUNT);//注册时返回一个账号 //存一次昵称sprintf(buf,"insert into Data values('%s','%s','%s','%s',0,%d)",account,sock->message,sock->message_tmp,sock->recv_Acount,sock->send_fd);//printf("%s\n",buf);mysql_query(mysql,buf);mysql_free_result(result);
}int Retrieve_server(recv_t *sock,MYSQL *mysql)
{int ret;char recv_buf[MAX_USERNAME];char buf[256];sprintf(buf,"select *from Data where Account = %s",sock->send_Account);mysql_query(mysql,buf);MYSQL_RES *result = mysql_store_result(mysql);MYSQL_ROW row;row=mysql_fetch_row(result);if(!strcmp(sock->message_tmp,row[2])){sprintf(buf,"update Data set password = \"%s\" where Account = \"%s\"",sock->message,sock->send_Account);mysql_query(mysql,buf);send_data(sock->send_fd,"y",32);}else send_data(sock->send_fd,"@@@",32);
}int add_friend_server(recv_t *sock,MYSQL *mysql)
{int ret;char recv_buf[MAX_USERNAME];char buf[256];sprintf(buf,"select *from Data where Account = %s",sock->recv_Acount);mysql_query(mysql,buf);MYSQL_RES *result = mysql_store_result(mysql);MYSQL_ROW row=mysql_fetch_row(result);int tmp=atoi(row[5]);if(atoi(row[4])==1) //在线{//printf("11\n");if(send(tmp,sock,sizeof(recv_t),0)<0) //根据账号查找到接收者的套接字perror("error in send\n");//需要在线消息盒子 否则无法实现}else //不在线把数据放到消息盒子{//printf("212\n");sprintf(buf,"insert into messages_box values('%d','%s','%s','%s')",tmp,sock->send_Account,sock->recv_Acount,sock->message);//printf("%s\n",buf);mysql_query(mysql,buf);}//成功后不发送消息
}int add_friend_server_already_agree(recv_t *sock,MYSQL *mysql)//向朋友数据库加入消息
{//friend数据表中第三项 是为了在删除时仅删除一项就把一对好友关系进行删除 //这个函数只需要操作下数据库就好char buf[512];char unique_for_del[64];Delete_for_friend_third(sock->recv_Acount,sock->send_Account,unique_for_del);unique_for_del[strlen(sock->recv_Acount)+strlen(sock->send_Account)+1]='\0';//printf("%s\n",unique_for_del);sprintf(buf,"insert into friend values('%s','%s','%s')",sock->recv_Acount,sock->send_Account,unique_for_del);//printf("加入数据库:%s\n",buf);mysql_query(mysql,buf);return 1;
}int del_friend_server(recv_t *sock,MYSQL *mysql)
{char buf[256];char unique_for_del[64];memset(unique_for_del,0,sizeof(unique_for_del)); //再说一遍 初始化及其重要 其中很可能有一些废数据Delete_for_friend_third(sock->recv_Acount,sock->send_Account,unique_for_del);unique_for_del[strlen(sock->recv_Acount)+strlen(sock->send_Account)+1]='\0';//printf("%s %s %s\n",sock->recv_Acount,sock->send_Account,unique_for_del);sprintf(buf,"delete from friend where del = '%s'",unique_for_del);//printf("%s\n",buf);mysql_query(mysql,buf);return 1;
}int List_friends_server(recv_t *sock,MYSQL *mysql) //因为数据库表建的不好 导致查找效率较低
{recv_t packet;packet.type=LIST_FRIENDS; //区别与EOF包的差别char send_account[MAX_ACCOUNT]; //请求好友列表者 strcpy(send_account,sock->send_Account);char buf[256];sprintf(buf,"select *from friend where account1 = '%s'",sock->send_Account);mysql_query(mysql,buf);MYSQL_RES *result = mysql_store_result(mysql);MYSQL_RES *res=NULL;int number=mysql_num_rows(result);MYSQL_ROW row,wor;//printf("第一遍搜索:%d:\n",number);while(number--)//第一遍搜索的好友总数{int ret = 0;bzero(&packet,sizeof(recv_t));row=mysql_fetch_row(result);//printf("开始搜索好友!\n");sprintf(buf,"select *from Data where Account = '%s'",row[1]);//每一个好友的信息//printf("%s\n",buf);mysql_query(mysql,buf);res=mysql_store_result(mysql);wor=mysql_fetch_row(res);strcpy(packet.message,wor[3]);//昵称strcpy(packet.message_tmp,row[1]);//好友账号//printf("%s\n",packet.message); //测试用packet.conn_fd=atoi(wor[4]);//是否在线packet.send_fd=atoi(wor[5]);//好友套接字//printf("好友信息 %d %s %s\n",sizeof(recv_t),packet.message,packet.message_tmp);if(( ret = send(sock->send_fd,&packet,sizeof(recv_t),0))<0)perror("error in list_friend send\n");//printf("hello! %d \n",ret );}mysql_free_result(result);mysql_free_result(res); //释放一遍空间//开始第二遍搜索 数据库表建的不好 不然可以一遍ok的sprintf(buf,"select *from friend where account2 = '%s'",sock->send_Account);mysql_query(mysql,buf);result = mysql_store_result(mysql);res=NULL;number=mysql_num_rows(result); //获取好友//printf("第二遍搜索:%d:\n",number);while(number--)//第二遍搜索的好友总数{bzero(&packet,sizeof(recv_t));row=mysql_fetch_row(result);bzero(&packet,sizeof(recv_t));sprintf(buf,"select *from Data where Account = '%s'",row[0]);//每一个好友的信息mysql_query(mysql,buf);res=mysql_store_result(mysql);wor=mysql_fetch_row(res);strcpy(packet.message,wor[3]);//昵称strcpy(packet.message_tmp,row[0]);//好友账号packet.conn_fd=atoi(wor[4]);//是否在线packet.send_fd=atoi(wor[5]);//好友套接字//printf("好友信息 %d %s %s\n",sizeof(recv_t),packet.message,packet.message_tmp);if((send(sock->send_fd,&packet,sizeof(recv_t),0))<0)perror("error in list_friend send\n");//printf("hello!\n");}bzero(&packet,sizeof(recv_t));packet.type=EOF_OF_BOX;//好友消息的结束包//printf("好友的结束包 %d\n",sizeof(recv_t));if((send(sock->send_fd,&packet,sizeof(recv_t),0))<0)perror("error in EOF list_friend\n");mysql_free_result(result);mysql_free_result(res);return 1;
}//接收到客户端的发送的消息 在数据库中判断接收者是否在线 在线直接发送 不然不用管了
//等对方上线时消息自动加载 写完后记得修改状态的问题 最好正常结束发一个结束包 修改请求
int send_messages_server(recv_t *sock,MYSQL *mysql)
{//判断是否在线 在线发送 然后把消息加入离线消息盒子(在线与不在线都要加入离线消息盒子 保存聊天信息)int ret;char recv_buf[MAX_USERNAME];char buf[256];sprintf(buf,"select *from Data where Account = '%s'",sock->recv_Acount);mysql_query(mysql,buf);MYSQL_RES *result = mysql_store_result(mysql);MYSQL_ROW row=mysql_fetch_row(result);list_status_t cuurps;int flagg=0;List_ForEach(status_per,cuurps){if(!strcmp(sock->recv_Acount,cuurps->account)){flagg=cuurps->fdd;break;}}if(flagg)//在线 直接发送 消息盒子接收 //其中消息进入后直接载入 不区分已读未读{recv_t package;strcpy(package.send_Account,sock->send_Account);strcpy(package.recv_Acount,sock->recv_Acount);strcpy(package.message_tmp,row[3]);strcpy(package.message,sock->message);package.type=SEND_MESSAGES;if(send(flagg,&package,sizeof(recv_t),0)<0) {perror("error in server send friend message\n");}}//开始把消息存入数据库 做标记 为好友信息mysql_free_result(result);sprintf(buf,"insert into messages_record values('%s','%s','%s','%s')",sock->send_Account,sock->send_Account,sock->recv_Acount,sock->message);//printf("%s\n",buf);mysql_query(mysql,buf);sprintf(buf,"insert into messages_record values('%s','%s','%s','%s')",sock->recv_Acount,sock->send_Account,sock->recv_Acount,sock->message);//printf("%s\n",buf);mysql_query(mysql,buf);//向消息记录数据库中加入消息 消息有两份//根据 ower_account 位来标记消息的所属者是谁 从而在登录时进行加载
}int register_group_server(recv_t *sock,MYSQL *mysql)
{char account[MAX_ACCOUNT];char buf[256];recv_t packet;memset(account,0,sizeof(account));mysql_query(mysql,"select *from Account");MYSQL_RES *result = mysql_store_result(mysql);MYSQL_ROW row=mysql_fetch_row(result);sprintf(account,"%d",atoi(row[0])+1);sprintf(buf,"update Account set Account = \"%s\" where Account = \"%s\"",account,row[0]);mysql_query(mysql,buf);//更新数据 保证不重复mysql_free_result(result);sprintf(buf,"insert into group_list values('%s','%s','%s','%d')",account,sock->send_Account,sock->message,OWNER);//把这个群存入数据库mysql_query(mysql,buf);//printf("注册得到的群号: %s\n",account);strcpy(packet.send_Account,account);packet.type=REGISTER_GROUP;if(send(sock->send_fd,&packet,sizeof(recv_t),0)<0)perror("error in register group !\n");
}int Add_group_server(recv_t *sock,MYSQL *mysql)
{char account[MAX_ACCOUNT];char buf[256];recv_t packet;memset(account,0,sizeof(account));sprintf(buf,"insert into group_list values('%s','%s','%s','%d')",sock->message,sock->recv_Acount,sock->message_tmp,COMMON);//成员姓名//printf("%s\n",buf);mysql_query(mysql,buf);bzero(&buf,sizeof(buf));sprintf(buf,"insert into group_messsges_list values('%s','%s','%s','%s','%d')",sock->message,sock->recv_Acount,sock->message_tmp,"Hello everyone!",COMMON);//成员姓名//printf("%s\n",buf);mysql_query(mysql,buf);mysql_query(mysql,"select *from group_list");MYSQL_RES *result = mysql_store_result(mysql);MYSQL_ROW row=mysql_fetch_row(result);
/* strcpy(packet.message,row[2]);packet.type=ADD_GROUP;if(send(sock->send_fd,&packet,sizeof(recv_t),0)<0) //发送群名称perror("error in send!(add group server)\n"); */return 1;
}int Quit_group_server(recv_t *sock,MYSQL *mysql)
{char buf[256];sprintf(buf,"delete from group_list where member_account = '%s' and group_account = '%s'",sock->recv_Acount,sock->message);mysql_query(mysql,buf);
}int Dissolve_server(recv_t *sock,MYSQL *mysql)//数据库中删除相关数据 ×两张表的数据
{int ret;char recv_buf[MAX_USERNAME];//登录时默认使用字符串int flag_recv=USERNAME;char buf[256];sprintf(buf,"delete from group_list where group_account = '%s'",sock->recv_Acount);//printf("%s\n",buf);mysql_query(mysql,buf);//直接删除即可 已经在客户端检测过有权限 sprintf(buf,"delete from group_messsges_list where group_account = '%s'",sock->recv_Acount);//printf("%s\n",buf);mysql_query(mysql,buf);//两张表中的数据都要删除//在这里其实可以改良 就是删除的时候给每一个成员发送一个包 消息盒子接收 随即删除//但是没时间了return 0;
}int Set_Admin_server(recv_t *sock,MYSQL *mysql)
{char buf[256];memset(buf,0,sizeof(buf));sprintf(buf,"update group_list set type = '%d' where group_account = '%s' and member_account'%s'",ADMIN,sock->message_tmp,sock->message);mysql_query(mysql,buf);//其实应该给每一个群员发送一个更新消息return 0;
}int Kicking_server(recv_t *sock,MYSQL *mysql)
{char buf[256];sprintf(buf,"delete from group_list where group_account = '%s' and member_account = '%s'",sock->message_tmp,sock->message);//删除掉此人//printf("%s\n",buf);mysql_query(mysql,buf);sprintf(buf,"delete from group_messsges_list where group_account = '%s' and member_account = '%s'",sock->message_tmp,sock->message);//删除掉此人所有消息//printf("%s\n",buf);mysql_query(mysql,buf);
}int Send_group_messages_server(recv_t *sock,MYSQL *mysql)//需要发送给每一个在线的好友
{char buf[256];bzero(&buf,sizeof(buf));sprintf(buf,"insert into group_messsges_list values('%s','%s','%s','%s','%d')",sock->recv_Acount,sock->send_Account,sock->message_tmp,sock->message,3);mysql_query(mysql,buf);//先把消息保存在服务器数据库中//然后把消息发送给每一个在线的好友bzero(&buf,sizeof(buf));sprintf(buf,"select *from group_list where group_account = '%s'",sock->recv_Acount);mysql_query(mysql,buf);MYSQL_RES *result = mysql_store_result(mysql);int ret=mysql_num_rows(result);MYSQL_ROW row;recv_t package;while(ret--){row=mysql_fetch_row(result);if(strcmp(row[1],sock->send_Account)) //发送给群内成员 不包括自己{//printf("%s %s \n",row[1],sock->send_Account);int flag = 0;int conn_fd=0;list_status_t curps;List_ForEach(status_per,curps){if(!strcmp(curps->account,row[1])){conn_fd=curps->fdd;break;}}if(!conn_fd) continue; //此成员不在线bzero(&package,sizeof(recv_t)); //清空缓冲区package.type=SEND_GROUP_MESSAGES;//发送至消息盒子strcpy(package.send_Account,sock->recv_Acount); //第二个参数是群号strcpy(package.message,sock->message);if(send(conn_fd,&package,sizeof(recv_t),0)<0)perror("error in send group messages\n");}}return 0;
}int Send_file_server(recv_t *recv_buf)
{recv_t package;bzero(&package,sizeof(recv_t));list_status_t ptr;int conn_fd=0;printf("进入发送文件函数\n");List_ForEach(status_per,ptr){printf("%s %s\n",recv_buf->recv_Acount,ptr->account);if(!strcmp(recv_buf->recv_Acount,ptr->account)){conn_fd=ptr->fdd; //证明在线//printf("找到在线\n");break;}}if(!conn_fd)//如果不在线转离线消息处理{//转为离线文件处理}package.type=RECV_FILE;strcpy(package.message_tmp,recv_buf->message_tmp);//文件名称strcpy(package.message,recv_buf->message);if(send(conn_fd,&package,sizeof(recv_t),0)<0)//消息盒子接收perror("error in send file in server\n");return 0;
}int *solve(void *arg)
{MYSQL mysql;mysql_init(&mysql); //初始化一个句柄mysql_library_init(0,NULL,NULL);//初始化数据库mysql_real_connect(&mysql,"127.0.0.1","root","lzl213260C","Login_Data",0,NULL,0);//连接数据库mysql_set_character_set(&mysql,"utf8");//调整为中文字符recv_t *recv_buf=(recv_t *)arg;int recv_flag=recv_buf->type;//printf("消息号码 : %d\n",recv_flag);switch (recv_flag){case LOGIN :login(recv_buf,&mysql);break;case REGISTER :register_server(recv_buf,&mysql);break;case RETRIEVE:Retrieve_server(recv_buf,&mysql);break;case ADD_FRIENDS:add_friend_server(recv_buf,&mysql);break;case ADD_FRIENDS_QUERY:add_friend_server_already_agree(recv_buf,&mysql);break;case DEL_FRIENDS:del_friend_server(recv_buf,&mysql);break;case LIST_FRIENDS:List_friends_server(recv_buf,&mysql);break;case SEND_MESSAGES:send_messages_server(recv_buf,&mysql);break;case REGISTER_GROUP:register_group_server(recv_buf,&mysql);break;case ADD_GROUP:Add_group_server(recv_buf,&mysql);break;case QUIT:Quit_group_server(recv_buf,&mysql);break;case DISSOLVE:Dissolve_server(recv_buf,&mysql);break;case SET_ADMIN:Set_Admin_server(recv_buf,&mysql);break;case KICKING:Kicking_server(recv_buf,&mysql);break;case SEND_GROUP_MESSAGES:Send_group_messages_server(recv_buf,&mysql);break;case SEND_FILE:Send_file_server(recv_buf);break;default:printf("error\n");break;}//printf("end of pthread!\n");struct epoll_event ev;ev.data.fd = recv_buf->conn_fd;ev.events = EPOLLIN | EPOLLONESHOT;//设置这个的目的是客户端在挂掉以后会发送一个信息 LT模式下没有接到包会不停的发 就会导致服务器epoll收到很多消息//解决方案是开始时事件类型改为那三个 然后设置EPOLLONESHOT 一个套接字只接受一次信息 在线程中在加上即可epoll_ctl(recv_buf->epfd, EPOLL_CTL_MOD,recv_buf->conn_fd, &ev);mysql_close(&mysql);free(recv_buf);
}
总是感觉写聊天室时有些心不在焉,导致项目并没有做到自己能达到的那样,还是希望能继续加油了
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
