目录
客户端(client)开发主要是以命令行的形式与服务器进行交互,无需人工输入json消息,以此来验证登录注册注销、添加好友与聊天、创建加入群组与群聊等功能。
1 总体思想
(1)命令解析:通过解析用户输入的命令来获取连接和获取相应的服务
(2)封装json:对服务器的请求先封装json消息,字段要与服务器端进行对齐,并进行序列化
(3)获取响应:对服务器的响应消息进行反序列化并显示
其主要内容包括:
(1)tcp连接:client与server进行socket连接
(2)选择业务类型:包括登录、注册、退出界面
(3)聊天页面:登录成功后进入菜单选择,实现添加好友与聊天、创建加入群组与群聊、注销功能
(4)接收消息:登陆成功后开子线程进行阻塞接收消息
2 关键部分的实现
1. TCP连接过程
解析命令行参数获取server的ip和port,创建client端socket并与server进行connect
if(argc < 3){
cerr<<"command invalid! example: ./Chatclient 127.0.0.1 6000"<<endl;
exit(-1);
}
//通过解析命令行参数传递的ip 和 port
char *ip = argv[1];
uint16_t port = atoi(argv[2]);
//创建client端的socket
int clientfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == clientfd){
cerr<<"socket create error"<<endl;
exit(-1);
}
//创建服务器ip+port
sockaddr_in server;
memset(&server,0,sizeof(sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr(ip);
//client与server进行连接
if(-1 == connect(clientfd,(sockaddr*)&server,sizeof(sockaddr_in))){
cerr<<"connect server error"<<endl;
close(clientfd);
exit(-1);
}
2. 登录过程
(1)先封装json消息,包含msgid、id和password,序列化为字符串发送给服务器
(2)发送成功后阻塞等待服务器的响应消息,响应消息包含好友列表消息、群组列表消息、离线消息(个人消息、群聊消息),将这些字符串反序列化为json消息并进行打印
(3)登陆后需要清空关于好友列表和群组列表的全局变量,因为这两个变量为vector,当用于下线再上线时会重复这些信息,所以需要清空
(4)main函数开辟的主线程作为发送消息的线程,登陆成功后需要开启一个接收线程,与发送线程并行,用于随时接收聊天消息和群聊消息,同时需要保证接收线程只开启一次,方式用户下线再上线重复开启接收线程造成资源浪费和数据不一致等问题
(5)之后进入聊天主菜单页面
case 1: // login
{
int id = 0;
char password[50]={0};//用getline可以读取一行字符串,string会残留回车
cout<<"userid:"<<endl;;
cin>>id;
cin.get();//读掉缓冲区残留的回车
cout<<"password:"<<endl;
cin.getline(password,50);
//json序列化
json js;
js["msgid"]=LOGIN_MSG;
js["id"]=id;
js["password"]=password;
string request = js.dump();
int len = send(clientfd,request.c_str(),strlen(request.c_str())+1,0);
if(len == -1){
cerr<<"send login msg error"<<request<<endl;
}
else{
//发送成功,阻塞等待发送消息的响应
char buffer[1024] = {0};
len = recv(clientfd,buffer,1024,0);
if(-1 == len){
cerr<<"recv login response error"<<endl;
}
else{
//json反序列化
json responsejs = json::parse(buffer);
if(0!= responsejs["errno"].get<int>()){//登录失败
cerr<<responsejs["errmsg"]<<endl;
}
else{//登录成功
//记录当前用户的id 和 name
g_currentUser.setId(responsejs["id"].get<int>());
g_currentUser.setName(responsejs["name"]);
//记录当前用户的好友列表信息
if(responsejs.contains("friends")){
//防止登录退出后重新登录导致信息重复
g_currentUserFriendList.clear();
vector<string> vec = responsejs["friends"];
for(string &str:vec){
//json反序列化
json js = json::parse(str);
User user;
user.setId(js["id"].get<int>());
user.setName(js["name"]);
user.setState(js["state"]);
g_currentUserFriendList.push_back(user);
}
}
//记录当前用户的群组列表信息
if(responsejs.contains("groups")){
//防止登录退出后重新登录导致信息重复
g_currentUserGroupList.clear();
vector<string> vec1 = responsejs["groups"];
for(string &str:vec1){
//json反序列化
json js = json::parse(str);
Group group;
group.setId(js["id"].get<int>());
group.setName(js["groupname"]);
group.setDesc(js["groupdesc"]);
vector<string> vec2= js["users"];
for(string &str:vec2){
json js = json::parse(str);
GroupUser groupuser;
groupuser.setId(js["id"].get<int>());
groupuser.setName(js["name"]);
groupuser.setState(js["state"]);
groupuser.setRole(js["role"]);
group.getUsers().push_back(groupuser);
}
g_currentUserGroupList.push_back(group);
}
}
//显示登录用户的基本信息
showCurrentUserData();
//显示当前用户的离线消息 个人聊天信息或者群组消息
if(responsejs.contains("of