聊天室(0)-项目源码-李兆龙

引言

假期生活马上就要结束了 其中最有意思的一个项目就要数这个聊天室 在进行这个项目的过程中接触到了很多新的知识和以前没有注意到的隐晦的知识点 所以分为三篇对项目进行一个总结 方便日后查阅和反思

Data.h

#include <stdlib.h>
#include <assert.h>

#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; i<paging.offset && pos!=list ; i++)        \
               pos=pos->next;                                    \
            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; i<offset; i++ )                     \
                pos=pos->next;                               \
            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<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<errno.h> 
#include<stdlib.h>
#include<pthread.h>
#include<signal.h>
#include<mysql/mysql.h>
#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<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<errno.h>
#include<pthread.h> 
#include<stdio.h>
#include"Data.h"
#include<map>
#include<stack>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<iostream>
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);
    //接收完离线消息盒子记录开始接收好友信息列表
    //实现为直接开始收包 最后一个结束包其中无数据 标记位为EOF
    FetchAll_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会把原链表free
        if(!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]]->nicknam
            printf(
                    "------------------------------------------------------------------\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<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<errno.h> 
#include<stdlib.h>
#include<pthread.h>
#include<signal.h>
#include<mysql/mysql.h>
#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<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<errno.h>
#include<pthread.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<mysql/mysql.h>
#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);
}

总是感觉写聊天室时有些心不在焉,导致项目并没有做到自己能达到的那样,还是希望能继续加油了

原文链接: https://www.cnblogs.com/lizhaolong/p/16437394.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    聊天室(0)-项目源码-李兆龙

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/395827

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月5日 下午1:44
下一篇 2023年4月5日 下午1:44

相关推荐