聊天室群聊以及私聊功能的实现

内容分享2小时前发布
0 0 0

之前的代码框架:part1

1 私聊功能的实现

1.1 客户端相关代码;

为客户端添加私聊功能,用户可以选择一名目标用户名,然后开始和目标进行私聊,指定目标后先发送目标名到服务器进行绑定,而后每条消息都发送到服务器,服务器再转发到目标用户。客户端有两个线程,一个线程用于发送信息给对方,另外一个线程接收信息。为服务器添加处理私聊业务的代码,服务器接收客户端发来的绑定信息和私聊,绑定目标用户并将每条私聊信息都转发到目标用户的客户端处。当用户输入 exit 时,能够离开私聊,返回到主界面。 要面向对象编程,进行类封装。

修改后的 HandleClient 函数:

void client::HandleClient(int conn){
    int choice;
    string name,pass,pass1;
    bool if_login=false;//记录是否登录成功
    string login_name;//记录成功登录的用户名

    cout<<" ------------------
";
    cout<<"|                  |
";
    cout<<"| 请输入你要的选项:|
";
    cout<<"|    0:退出        |
";
    cout<<"|    1:登录        |
";
    cout<<"|    2:注册        |
";
    cout<<"|                  |
";
    cout<<" ------------------ 

";

    //开始处理注册、登录事件
    while(1){
        if(if_login)
           break;
        cin>>choice;
        if(choice==0)
            break;
        //注册
        else if(choice==2){
            cout<<"注册的用户名:";
            cin>>name;
            while(1){
                cout<<"密码:";
                cin>>pass;
                cout<<"确认密码:";
                cin>>pass1;
                if(pass==pass1)
                    break;
                else
                    cout<<"两次密码不一致!

";
            }
            name="name:"+name;
            pass="pass:"+pass;
            string str=name+pass;
            send(conn,str.c_str(),str.length(),0);
            cout<<"注册成功!
";
            cout<<"
继续输入你要的选项:";
        }
        //登录
        else if(choice==1&&!if_login){
            while(1){
                cout<<"用户名:";
                cin>>name;
                cout<<"密码:";
                cin>>pass;
                string str="login"+name;
                str+="pass:";
                str+=pass;
                send(sock,str.c_str(),str.length(),0);//发送登录信息
                char buffer[1000];
                memset(buffer,0,sizeof(buffer));
                recv(sock,buffer,sizeof(buffer),0);//接收响应
                string recv_str(buffer);
                if(recv_str.substr(0,2)=="ok"){
                    if_login=true;
                    login_name=name;
                    cout<<"登录成功

";
                    break;
                }
                else
                    cout<<"密码或用户名错误!

";
            }
        }
    }
    //登录成功
    while(if_login&&1){
        if(if_login){
            system("clear");
            cout<<"        欢迎回来,"<<login_name<<endl;
            cout<<" -------------------------------------------
";
            cout<<"|                                           |
";
            cout<<"|          请选择你要的选项:               |
";
            cout<<"|              0:退出                       |
";
            cout<<"|              1:发起单独聊天               |
";
            cout<<"|              2:发起群聊                   |
";
            cout<<"|                                           |
";
            cout<<" ------------------------------------------- 

";
        }
        cin>>choice;
        if(choice==0)
            break;
        //私聊
        if(choice==1){
            cout<<"请输入对方的用户名:";
            string target_name,content;
            cin>>target_name;
            string sendstr("target:"+target_name+"from:"+login_name);//标识目标用户+源用户
            send(sock,sendstr.c_str(),sendstr.length(),0);//先向服务器发送目标用户、源用户
            cout<<"请输入你想说的话(输入exit退出):
";
            thread t1(client::SendMsg,conn); //创建发送线程
            thread t2(client::RecvMsg,conn);//创建接收线程
            t1.join();
            t2.join();//t1 t2线程结束才能结束该线程;
        }
    }
    close(sock);
}
修改后的 SendMsg 函数:

发送形式为:content:xxxxxxx


//注意,前面不用加static!
void client::SendMsg(int conn){
    while (1)
    {
        string str;
        cin>>str;
        //发送消息
        str="content:"+str;
        int ret=send(conn, str.c_str(), str.length(),0); //发送
        //输入exit或者对端关闭时结束
        if(str=="content:exit"||ret<=0)
            break;
    }
}
修改后的 RecvMsg 函数:

//注意,前面不用加static!
void client::RecvMsg(int conn){
    //接收缓冲区
    char buffer[1000];
    //不断接收数据
    while(1)
    {
        memset(buffer,0,sizeof(buffer));
        int len = recv(conn, buffer, sizeof(buffer),0);
        //recv返回值小于等于0,退出
        if(len<=0)
            break;
        cout<<buffer<<endl;
    }
}

1.2 服务端相关代码

RecvMsg代码:

加入四元组记录每个端口相关信息;


void server::RecvMsg(int conn){
    tuple<bool,string,string,int> info;//元组类型,四个成员分别为if_login、login_name、target_name、target_conn
    /*
        bool if_login;//记录当前服务对象是否成功登录
        string login_name;//记录当前服务对象的名字
        string target_name;//记录目标对象的名字
        int target_conn;//目标对象的套接字描述符
    */
    get<0>(info)=false;//把if_login置为false
    get<3>(info)=-1;//target_conn置为-1

    //接收缓冲区
    char buffer[1000];
    //不断接收数据
    while(1)
    {
        memset(buffer,0,sizeof(buffer));
        int len = recv(conn, buffer, sizeof(buffer),0);
        //客户端发送exit或者异常结束时,退出
        if(strcmp(buffer,"content:exit")==0 || len<=0){
            close(conn);
            sock_arr[conn]=false;
            break;
        }
        cout<<"收到套接字描述符为"<<conn<<"发来的信息:"<<buffer<<endl;
        string str(buffer);
        HandleRequest(conn,str,info);
    }
}
新增加成员变量

unordered_map<string,int> name_sock_map;
记录名字和文件描述符;

pthread_mutex_t server::name_sock_mutx;
互斥锁,锁住需要修改name_sock_map的临界区;

修改后的 server.h


#ifndef SERVER_H
#define SERVER_H

#include "global.h"

class server{
    private:

        int server_port;
        int server_sockfd;
        string server_ip;
        static vector<bool> sock_arr;
        static unordered_map<string,int> name_sock_map;//名字和套接字描述符
        static pthread_mutex_t name_sock_mutx;//互斥锁,锁住需要修改name_sock_map的临界区
    public:
        server(int port,string ip);
        ~server();
        void run();
        static void RecvMsg(int conn);
        static void HandleRequest(int conn,string str,tuple<bool,string,string,int> &info);
};
#endif

构造函数:


#include "server.h"

vector<bool> server::sock_arr(10000,false);
unordered_map<string,int> server::name_sock_map;//名字和套接字描述符
pthread_mutex_t server::name_sock_mutx;//互斥锁,锁住需要修改name_sock_map的临界区

server::server(int port,string ip):server_port(port),server_ip(ip){
    pthread_mutex_init(&name_sock_mutx, NULL); //创建互斥锁
}
server::~server(){
    ...
修改后的 HandleRequest 函数:

void server::HandleRequest(int conn,string str,tuple<bool,string,string,int> &info){
    char buffer[1000];
    string name,pass;
    //把参数提出来,方便操作
    bool if_login=get<0>(info);//记录当前服务对象是否成功登录
    string login_name=get<1>(info);//记录当前服务对象的名字
    string target_name=get<2>(info);//记录目标对象的名字
    int target_conn=get<3>(info);//目标对象的套接字描述符

    //连接MYSQL数据库
    MYSQL *con=mysql_init(NULL);
    mysql_real_connect(con,"127.0.0.1","root","","ChatProject",0,NULL,CLIENT_MULTI_STATEMENTS);

    //注册
    if(str.find("name:")!=str.npos){
        int p1=str.find("name:"),p2=str.find("pass:");
        name=str.substr(p1+5,p2-5);
        pass=str.substr(p2+5,str.length()-p2-4);
        string search="INSERT INTO USER VALUES ("";
        search+=name;
        search+="","";
        search+=pass;
        search+="");";
        cout<<"sql语句:"<<search<<endl<<endl;
        mysql_query(con,search.c_str());
    }
    //登录
    else if(str.find("login")!=str.npos){
        int p1=str.find("login"),p2=str.find("pass:");
        name=str.substr(p1+5,p2-5);
        pass=str.substr(p2+5,str.length()-p2-4);
        string search="SELECT * FROM USER WHERE NAME="";
        search+=name;
        search+="";";
        cout<<"sql语句:"<<search<<endl;
        auto search_res=mysql_query(con,search.c_str());
        auto result=mysql_store_result(con);
        int col=mysql_num_fields(result);//获取列数
        int row=mysql_num_rows(result);//获取行数
        //查询到用户名
        if(search_res==0&&row!=0){
            cout<<"查询成功
";
            auto info=mysql_fetch_row(result);//获取一行的信息
            cout<<"查询到用户名:"<<info[0]<<" 密码:"<<info[1]<<endl;
            //密码正确
            if(info[1]==pass){
                cout<<"登录密码正确

";
                string str1="ok";
                if_login=true;
                login_name=name;
                pthread_mutex_lock(&name_sock_mutx); //上锁
                name_sock_map[login_name]=conn;//记录下名字和文件描述符的对应关系
                pthread_mutex_unlock(&name_sock_mutx); //解锁
                send(conn,str1.c_str(),str1.length()+1,0);
            }
            //密码错误
            else{
                cout<<"登录密码错误

";
                char str1[100]="wrong";
                send(conn,str1,strlen(str1),0);
            }
        }
        //没找到用户名
        else{
            cout<<"查询失败

";
            char str1[100]="wrong";
            send(conn,str1,strlen(str1),0);
        }
    }
    //设定目标的文件描述符
    else if(str.find("target:")!=str.npos){
        int pos1=str.find("from");
        string target=str.substr(7,pos1-7),from=str.substr(pos1+4);
        target_name=target;
        //找不到这个目标
        if(name_sock_map.find(target)==name_sock_map.end())
            cout<<"源用户为"<<login_name<<",目标用户"<<target_name<<"仍未登录,无法发起私聊
";
        //找到了目标
        else{
            cout<<"源用户"<<login_name<<"向目标用户"<<target_name<<"发起的私聊即将建立";
            cout<<",目标用户的套接字描述符为"<<name_sock_map[target]<<endl;
            target_conn=name_sock_map[target];
        }
    }

    //接收到消息,转发
    else if(str.find("content:")!=str.npos){
        if(target_conn==-1){
            cout<<"找不到目标用户"<<target_name<<"的套接字,将尝试重新寻找目标用户的套接字
";
            if(name_sock_map.find(target_name)!=name_sock_map.end()){
                target_conn=name_sock_map[target_name];
                cout<<"重新查找目标用户套接字成功
";
            }
            else{
                cout<<"查找仍然失败,转发失败!
";
            }
        }
        string recv_str(str);
        string send_str=recv_str.substr(8);
        cout<<"用户"<<login_name<<"向"<<target_name<<"发送:"<<send_str<<endl;
        send_str="["+login_name+"]:"+send_str;
        send(target_conn,send_str.c_str(),send_str.length(),0);
    }

    //更新实参
    get<0>(info)=if_login;//记录当前服务对象是否成功登录
    get<1>(info)=login_name;//记录当前服务对象的名字
    get<2>(info)=target_name;//记录目标对象的名字
    get<3>(info)=target_conn;//目标对象的套接字描述符
}

2 群聊功能的实现

2.1 客户端功能实现:

HandleClient函数

首先修改客户端的 HandleClient 函数,当用户选择群聊时,我们先让其输入要加入的群号,并发送一个格式为“group:xxx”(xxx 为群号)的报文给服务器进行绑定,绑定完之后我们创建一个发送线程和一个接收线程,分别用来发送信息和接收信息,并让主线程等待两个线程返回。同时为了让发送线程能区分私聊和群聊,我们将群聊时传入 SendMsg 设为负数(即套接字描述符的相反数)。


void client::HandleClient(int conn){
    int choice;
    string name,pass,pass1;
    bool if_login=false;//记录是否登录成功
    string login_name;//记录成功登录的用户名

    cout<<" ------------------
";
    cout<<"|                  |
";
    cout<<"| 请输入你要的选项:|
";
    cout<<"|    0:退出        |
";
    cout<<"|    1:登录        |
";
    cout<<"|    2:注册        |
";
    cout<<"|                  |
";
    cout<<" ------------------ 

";

    //开始处理注册、登录事件
    while(1){
        if(if_login)
           break;
        cin>>choice;
        if(choice==0)
            break;
        //注册
        else if(choice==2){
            cout<<"注册的用户名:";
            cin>>name;
            while(1){
                cout<<"密码:";
                cin>>pass;
                cout<<"确认密码:";
                cin>>pass1;
                if(pass==pass1)
                    break;
                else
                    cout<<"两次密码不一致!

";
            }
            name="name:"+name;
            pass="pass:"+pass;
            string str=name+pass;
            send(conn,str.c_str(),str.length(),0);
            cout<<"注册成功!
";
            cout<<"
继续输入你要的选项:";
        }
        //登录
        else if(choice==1&&!if_login){
            while(1){
                cout<<"用户名:";
                cin>>name;
                cout<<"密码:";
                cin>>pass;
                string str="login"+name;
                str+="pass:";
                str+=pass;
                send(sock,str.c_str(),str.length(),0);//发送登录信息
                char buffer[1000];
                memset(buffer,0,sizeof(buffer));
                recv(sock,buffer,sizeof(buffer),0);//接收响应
                string recv_str(buffer);
                if(recv_str.substr(0,2)=="ok"){
                    if_login=true;
                    login_name=name;
                    cout<<"登录成功

";
                    break;
                }
                else
                    cout<<"密码或用户名错误!

";
            }
        }
    }
    //登录成功
    while(if_login&&1){
        if(if_login){
            system("clear");
            cout<<"        欢迎回来,"<<login_name<<endl;
            cout<<" -------------------------------------------
";
            cout<<"|                                           |
";
            cout<<"|          请选择你要的选项:               |
";
            cout<<"|              0:退出                       |
";
            cout<<"|              1:发起单独聊天               |
";
            cout<<"|              2:发起群聊                   |
";
            cout<<"|                                           |
";
            cout<<" ------------------------------------------- 

";
        }
        cin>>choice;
        if(choice==0)
            break;
        //私聊
        else if(choice==1){
            cout<<"请输入对方的用户名:";
            string target_name,content;
            cin>>target_name;
            string sendstr("target:"+target_name+"from:"+login_name);//标识目标用户+源用户
            send(sock,sendstr.c_str(),sendstr.length(),0);//先向服务器发送目标用户、源用户
            cout<<"请输入你想说的话(输入exit退出):
";
            thread t1(client::SendMsg,conn); //创建发送线程
            thread t2(client::RecvMsg,conn);//创建接收线程
            t1.join();
            t2.join();
        }
        //群聊
        else if(choice==2){
            cout<<"请输入群号:";
            int num;
            cin>>num;
            string sendstr("group:"+to_string(num));
            send(sock,sendstr.c_str(),sendstr.length(),0);
            cout<<"请输入你想说的话(输入exit退出):
";
            thread t1(client::SendMsg,-conn); //创建发送线程,传入负数,和私聊区分开
            thread t2(client::RecvMsg,conn);//创建接收线程
            t1.join();
            t2.join();
        }
    }
    close(sock);
}
SendMsg函数

加入判断看发送群聊消息还是私聊消息;


//注意,前面不用加static!
void client::SendMsg(int conn){
    while (1)
    {
        string str;
        cin>>str;
        //私聊消息
        if(conn>0){
            str="content:"+str;
        }
        //群聊信息
        else if(conn<0){
            str="gr_message:"+str;
        }
        int ret=send(abs(conn), str.c_str(), str.length(),0); //发送
        //输入exit或者对端关闭时结束
        if(str=="content:exit"||ret<=0)
            break;
    }
}

2.2服务端

加入set,set中含有加入该群聊的客户端文件描述符;

server.h

#ifndef SERVER_H
#define SERVER_H

#include "global.h"

class server{
    private:
        int server_port;
        int server_sockfd;
        string server_ip;
        static vector<bool> sock_arr;
        static unordered_map<string,int> name_sock_map;//名字和套接字描述符
        static pthread_mutex_t name_sock_mutx;//互斥锁,锁住需要修改name_sock_map的临界区
        static unordered_map<int,set<int> > group_map;//记录群号和套接字描述符集合
        static pthread_mutex_t group_mutx;//互斥锁,锁住需要修改group_map的临界区
    public:
        server(int port,string ip);
        ~server();
        void run();
        static void RecvMsg(int conn);
        static void HandleRequest(int conn,string str,tuple<bool,string,string,int,int> &info);     //加多了一个int
};
#endif
初始化定义

unordered_map<int,set<int> > server::group_map;//记录群号和套接字描述符集合
pthread_mutex_t server::group_mutx;//互斥锁,锁住需要修改group_map的临界区

tuple<bool,string,string,int,int> info;//元组类型,四个成员分别为if_login、login_name、target_name、target_conn
/*
    bool if_login;//记录当前服务对象是否成功登录
    string login_name;//记录当前服务对象的名字
    string target_name;//记录目标对象的名字
    int target_conn;//目标对象的套接字描述符
    int group_num;//记录所处群号
*/
handleRequest

void server::HandleRequest(int conn,string str,tuple<bool,string,string,int,int> &info){
    ...
    int group_num=get<4>(info);//记录所处群号
    ...
    //绑定群聊号
    else if(str.find("group:")!=str.npos){
        string recv_str(str);
        string num_str=recv_str.substr(6);
        group_num=stoi(num_str);
        cout<<"用户"<<login_name<<"绑定群聊号为:"<<num_str<<endl;
        pthread_mutex_lock(&group_mutx);//上锁
        group_map[group_num].insert(conn);
        pthread_mutex_unlock(&group_mutx);//解锁
    }

    //广播群聊信息
    else if(str.find("gr_message:")!=str.npos){
        string send_str(str);
        send_str=send_str.substr(11);
        send_str="["+login_name+"]:"+send_str;
        cout<<"群聊信息:"<<send_str<<endl;
        for(auto i:group_map[group_num]){
            if(i!=conn)
                send(i,send_str.c_str(),send_str.length(),0);
        }
    }
    ...
© 版权声明

相关文章

暂无评论

none
暂无评论...