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

编程入门 行业动态 更新时间:2024-10-28 03:31:34

<a href=https://www.elefans.com/category/jswz/34/1768719.html style=聊天室群聊以及私聊功能的实现"/>

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

之前的代码框架:part1

1 私聊功能的实现

1.1 客户端相关代码;

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

修改后的 HandleClient 函数:

void client::HandleClient(int conn){int choice;string name,pass,pass1;bool if_login=false;//记录是否登录成功string login_name;//记录成功登录的用户名cout<<" ------------------\n";cout<<"|                  |\n";cout<<"| 请输入你要的选项:|\n";cout<<"|    0:退出        |\n";cout<<"|    1:登录        |\n";cout<<"|    2:注册        |\n";cout<<"|                  |\n";cout<<" ------------------ \n\n";//开始处理注册、登录事件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;elsecout<<"两次密码不一致!\n\n";}name="name:"+name;pass="pass:"+pass;string str=name+pass;send(conn,str.c_str(),str.length(),0);cout<<"注册成功!\n";cout<<"\n继续输入你要的选项:";}//登录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<<"登录成功\n\n";break;}elsecout<<"密码或用户名错误!\n\n";}}}//登录成功while(if_login&&1){if(if_login){system("clear");cout<<"        欢迎回来,"<<login_name<<endl;cout<<" -------------------------------------------\n";cout<<"|                                           |\n";cout<<"|          请选择你要的选项:               |\n";cout<<"|              0:退出                       |\n";cout<<"|              1:发起单独聊天               |\n";cout<<"|              2:发起群聊                   |\n";cout<<"|                                           |\n";cout<<" ------------------------------------------- \n\n";}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退出):\n";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置为falseget<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<<"查询成功\n";auto info=mysql_fetch_row(result);//获取一行的信息cout<<"查询到用户名:"<<info[0]<<" 密码:"<<info[1]<<endl;//密码正确if(info[1]==pass){cout<<"登录密码正确\n\n";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<<"登录密码错误\n\n";char str1[100]="wrong";send(conn,str1,strlen(str1),0);}}//没找到用户名else{cout<<"查询失败\n\n";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<<"仍未登录,无法发起私聊\n";//找到了目标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<<"的套接字,将尝试重新寻找目标用户的套接字\n";if(name_sock_map.find(target_name)!=name_sock_map.end()){target_conn=name_sock_map[target_name];cout<<"重新查找目标用户套接字成功\n";}else{cout<<"查找仍然失败,转发失败!\n";}}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<<" ------------------\n";cout<<"|                  |\n";cout<<"| 请输入你要的选项:|\n";cout<<"|    0:退出        |\n";cout<<"|    1:登录        |\n";cout<<"|    2:注册        |\n";cout<<"|                  |\n";cout<<" ------------------ \n\n";//开始处理注册、登录事件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;elsecout<<"两次密码不一致!\n\n";}name="name:"+name;pass="pass:"+pass;string str=name+pass;send(conn,str.c_str(),str.length(),0);cout<<"注册成功!\n";cout<<"\n继续输入你要的选项:";}//登录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<<"登录成功\n\n";break;}elsecout<<"密码或用户名错误!\n\n";}}}//登录成功while(if_login&&1){if(if_login){system("clear");cout<<"        欢迎回来,"<<login_name<<endl;cout<<" -------------------------------------------\n";cout<<"|                                           |\n";cout<<"|          请选择你要的选项:               |\n";cout<<"|              0:退出                       |\n";cout<<"|              1:发起单独聊天               |\n";cout<<"|              2:发起群聊                   |\n";cout<<"|                                           |\n";cout<<" ------------------------------------------- \n\n";}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退出):\n";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退出):\n";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);}}...

更多推荐

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

本文发布于:2023-07-28 19:54:36,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1293529.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:聊天室   功能

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!