集群聊天服务器(11)客户端开发
目录
- 首页面功能开发
- 添加好友和聊天
- 帮助和添加好友
- 聊天功能
- 创建群组
- 添加群组
- 群组聊天
- 退出
- 测试问题
- 一对一聊天
- 第一次发送两个离线消息只收到一个
- 创建和加入群组
首页面功能开发
#include "json.hpp"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <chrono>
#include <ctime>
using namespace std;
using json=nlohmann::json;
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "group.hpp"
#include "user.hpp"
#include "public.hpp"
//记录当前系统登录的用户信息
User g_currentUser;
//记录当前登录用户的好友列表信息
vector<User> g_currentUserFriendList;
//记录当前登录用户的群组列表信息
vector<Group> g_currentUserGroupList;
//显示当前登录成功用户的基本信息
void showCurrentUserData();
//接收线程
void readTaskHandler(int clientfd);
//获取系统时间(聊天信息需要添加时间信息)
string getCurrentTime();
//主聊天页面程序
void mainMenu();
//聊天客户端程序实现,main线程用作发送线程,子线程用作接收线程
int main(int argc,char **argv)
{
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);
}
//填写client需要连接的server信息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);
}
//main线程用于接收用户输入,负责发送数据
for(;;)
{
//显示首页面菜单 登录、注册、退出
cout<<"========================="<<endl;
cout<<"1.login"<<endl;
cout<<"2.register"<<endl;
cout<<"3.quit"<<endl;
cout<<"========================="<<endl;
cout<<"choice:";
int choice=0;
cin>>choice;
cin.get();//读掉缓冲区残留的回车
switch(choice)
{
case 1: //login业务
{
int id=0;
char pwd[50]={0};
cout<<"userid:";
cin>>id;
cin.get();//读掉缓冲区残留的回车
cout<<"userpassword:";
cin.getline(pwd,50);
json js;
js["msgid"]=LOGIN_MSG;
js["id"]=id;
js["password"]=pwd;
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 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"))
{
vector<string> vec=responsejs["friends"];
for (string &str:vec)
{
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"))
{
vector<string> vec1=responsejs["groups"];
for(string &groupstr:vec1)
{
json grpjs=json::parse(groupstr);
Group group;
group.setId(grpjs["id"].get<int>());
group.setName(grpjs["groupname"]);
group.setDesc(grpjs["groupdesc"]);
vector<string> vec2=grpjs["users"];
for (string &userstr:vec2)
{
GroupUser user;
json js=json::parse(userstr);
user.setId(js["id"].get<int>());
user.setName(js["name"]);
user.setState(js["state"]);
user.setRole(js["role"]);
group.getUsers().push_back(user);
}
g_currentUserGroupList.push_back(group);
}
}
//显示登录用户的基本信息
showCurrentUserData();
//显示当前用户的离线消息 个人聊天或者群组消息
if(responsejs.contains("offlinemsg"))
{
vector<string> vec=responsejs["offlinemsg"];
for(string &str:vec)
{
json js=json::parse(str);
//time+[id]+name+"said:"+xxx
cout<<js["time"]<<"["<<js["id"]<<"]"<<js["name"]<<"said:"<<js["msg"]<<endl;
}
}
//登录成功
std::thread readTask(readTaskHandler,clientfd);
readTask.detach();
//进入聊天主菜单页面
mainMenu();
}
}
}
}
break;
case 2://register业务
{
char name[50]={0};
char pwd[50]={0};
cout<<"username:";
cin.getline(name,50);
cout<<"userpassword:";
cin.getline(pwd,50);
json js;
js["msgid"]=REG_MSG;
js["name"]=name;
js["password"]=pwd;
string request =js.dump();
int len=send(clientfd,request.c_str(),strlen(request.c_str())+1,0);
if(len==-1)
{
cerr<<"send reg msg error:"<<request<<endl;
}
else{
char buffer[1024]={0};
len=recv(clientfd,buffer,1024,0);
if(-1==len)
{
cerr<<"recv reg response error"<<endl;
}
else{
json responsejs =json::parse(buffer);
if(0!=responsejs["errno"].get<int>())//注册失败
{
cerr<<name<<" is already exist,register error!"<<endl;
}
else//注册成功
{
cout<<name<<"register success,userid is"<<responsejs["id"]<<",do not forget it!"<<endl;
}
}
}
}
break;
case 3://quit业务
close(clientfd);
exit(0);
default:
cerr<<"invalid input!"<<endl;
break;
}
}
return 0;
}
//接收线程
void readTaskHandler(int clientfd)
{
}
//主聊天页面程序
void mainMenu()
{
}
//显示当前登录成功用户的基本信息
void showCurrentUserData()
{
cout<<"==========================login user==========================="<<endl;
cout<<"current login user=> id:"<<g_currentUser.getId()<<"name:"<<g_currentUser.getName();
cout<<"--------------------------friend list---------------------------"<<endl;
if(!g_currentUserFriendList.empty())
{
for(User &user:g_currentUserFriendList)
{
cout<<user.getId()<<" "<<user.getName()<<" "<<user.getState()<<endl;
}
}
cout<<"---------------------------group list-----------------------------"<<endl;
if(!g_currentUserGroupList.empty())
{
for(Group &group:g_currentUserGroupList)
{
cout<<group.getId()<<" "<<group.getName()<<" "<<group.getDesc()<<endl;
for(GroupUser &user:group.getUsers())
{
cout<<user.getId()<<" "<<user.getName()<<" "<<user.getState()<<user.getRole()<<endl;
}
}
}
cout<<"====================================================="<<endl;
}
string getCurrentTime()
{
auto tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
struct tm *ptm = localtime(&tt);
char date[60] = {0};
sprintf(date, "%d-%02d-%02d %02d:%02d:%02d",
(int)ptm->tm_year + 1900, (int)ptm->tm_mon + 1, (int)ptm->tm_mday,
(int)ptm->tm_hour, (int)ptm->tm_min, (int)ptm->tm_sec);
return std::string(date);
}
添加好友和聊天
其实就是之前只做服务器时,需要自己写json请求给服务器反序列化,而写客户端就是自己制作json,获取用户输入然后json序列化然后发给服务器
帮助和添加好友
聊天功能
创建群组
添加群组
群组聊天
退出
测试问题
发现我发消息时报段错误,和类型相关的
然后我发现是两边的json不对等,一个是toid,一个是to
所以说客户端制作的js和服务器端一定要一样
这样就可以接收离线消息
一对一聊天
第一次发送两个离线消息只收到一个
一个是数据库表设计离线消息表的时候,不能将userid设为主键,这样的话只会出现一个消息,离线时同时一个user发几个也只会有一个
对于我自己来说,是服务器db那边代码用中文乱码报错了,没有转成utf-8
现在好了
然后也能够体会到,在服务器开发的时候,一定要有日志,错误提示,这样遇到问题很好定位