之前的代码框架: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);
}
}
...
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...


