errorbeep@home:~$

群聊(互斥锁, set)


续:私聊(互斥锁, map, tuple)。增添用户群聊功能,当用户成功登录后选择“群聊”选项时,让其输入要加入的群号,随后可以在该群中和多位用户一起聊天。

为客户端添加群聊功能,用户可以选择加入一个群聊,指定群号后先发送到服务器进行绑定,而后每条消息都发送到服务器,服务器再广播给群里所有的用户;当用户输入 exit 时,能够离开私聊,返回到主界面。为服务器添加处理群聊业务的代码,服务器接收客户端发来的绑定信息和群聊信息,绑定目标用户并将每条群聊信息都广播发送给群里所有用户的客户端处;

实现群聊功能要先建立群号和群中所有成员对应的客户端的套接字描述符的一对多关系,使用unordered_map和set容器可以实现;和私聊功能的实现类似,客户端将用户输入的群号发送到服务端,服务端将该群号存储到用于描述客户端状态的元组中备用;接着客户端将用户发送的消息封装成群聊消息发送到服务端,服务端接收到群聊消息请求后将消息内容解析出后发送到除来源客户端外的所有群成员客户端;

客户端封装群聊消息并发送至客户端的函数和私聊功能中发送私聊消息的函数相同以做到代码重用,为了区分要封装成群聊消息还是私聊消息可以将传递的客户端套接字取反,即在函数内部若判断套接字为负则封装为群聊消息,否则为私聊消息;为不影响服务端使用在封装后向服务端发送消息时再取绝对值;

服务端将群号到群成员的映射保存为静态变量,每一个群号对应一组群成员,一组群成员可以使用set容器保存,最后将群号和set对象保存在unordered_map中;该映射也需要互斥锁维护;

服务端向群中每个用户对应客户端发送消息时,可以使用C++ 11的foreach遍历set:

for(type variable_name : array/vector_name/set...)
{
    loop statements
    ...
}

HandleClient中对用户群聊选项的支持:

//群聊
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退出):\n";
    thread t1(client::SendMsg,-conn); //创建发送线程,传入负数,和私聊区分开
    thread t2(client::RecvMsg,conn);//创建接收线程
    t1.join();
    t2.join();
}

客户端封装并发送消息的函数:

//注意,前面不用加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;
    }
}

服务端的定义:

#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

服务端处理群聊请求的函数:

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);
        }
    }
    ...