基础需求
简易版聊天室,仅为演示自定义协议,所以只添加了登录登出功能。
代码部分相当粗糙,很多场景没有进行细致考虑,仅展现了一个思路。
首先进行一下基本流程分析
- 服务端启动以后,监听某个地址和端口,接收新的客户端连接。
- 连接建立以后,客户端发送登录请求,服务端进行校验并返回请求的结果。
- 如果验证通过,服务端将客户端接入聊天室并提供服务
- 客户端此时可以发送消息以及看到其他客户端的聊天消息
- 当客户端退出时,发起登出请求。
由上可以得出客户端请求类型
- 登录请求
- 登出请求
- 查看群组
- 加入群组
- 退出群组
- 发送消息
- 接收消息
协议制定
以整个服务端为一个聊天室,为了支持登录登出功能,需要一个数据结构来记录当前聊天室内的用户以及状态(在线、离线),由于不涉及私聊功能以及聊天记录功能,我们设计出如下的数据结构:
struct User{
std::string name; //用户昵称
std::string ip; //ip地址
std::string usr; //登录用户名
std::string pwd; //登录密码
unsigned short port;//端口号
bool online; //在线状态
int fd; //socket描述符,方便直接发送消息
};
struct Group{
std::string name; //群组名称
std::list<User> userArray; //该群组用户列表
};
在服务端启动时,全局维护两个数据库,有登录请求时可查找userList验证,发送消息时根据发送给指定群组内的成员。
std::list<Group> groupList; //记录群组
std::list<User> userList; //记录用户列表
然后就涉及到通信协议的实现,简单思路如下:客户端向服务端发起一个Request,服务端接收后发送Response给客户端。Request需要包含具体操作命令,可能需要携带一部分数据,也可能什么数据都没有;Response在Request的基础上,需要添加一个该命令的执行结果。
//命令字
enum Cmd:unsigned int{
None,
Login, //登录
Logout, //登出
ListGroup, //列出所有群组
JoinGroup, //加入群组(未实现)
LeaveGroup, //离开群组(未实现)
SendMessage, //发送消息
RecvMessage //主动接收消息
};
//响应码
enum ErrorCode{
Unknown,
Success,
Failure
};
//请求结构
struct Request{
Cmd cmd; //命令字
unsigned int len; //数据长度
char body[]; //可变长数据体
};
//响应结果
struct Response{
Cmd cmd; //命令字
ErrorCode code; //响应码
int len; //数据长度
char body[]; //可变长数据体
};
定义完请求响应的结构,接下来就是请求响应如何解码。
由于对服务端来说客户端发送数据是不定时、不定长的,所以需要针对每一个连接开辟一个缓存区用来存放客户端发送的数据,而后在满足解包条件后,将数据提取解包,并清除该部分缓存。
// 注:这里使用std::vector<char> data作为客户端buffer缓存区
// 解Request
std::vector<Request*> unpackRequest(std::vector<char> & data){
std::vector<Request*> requests;
int headLen = sizeof (Request);
do{
//解头部
if(data.size()<headLen){
break;
}
Request head{};
memcpy(&head,data.data(),sizeof (Request));
if(data.size()-headLen<head.len){
break;
}
//解body
auto * req = (Request *)new char [headLen+head.len];
memcpy(req,data.data(),headLen+head.len);
requests.emplace_back(req);
//解包完成后清除该部分数据
data.erase(data.begin(),data.begin()+headLen+req->len);
}while(data.size()>=headLen);
return requests;
}
// 解Response
std::vector<Response*> unpackResponse(std::vector<char> & data){
std::vector<Response*> responses;
int headLen = sizeof (Response);
do{
//解头部
if(data.size()<headLen){
break;
}
Response head{};
memcpy(&head,data.data(),sizeof (Response));
if(data.size()-headLen<head.len){
break;
}
//解body
auto * res = (Response *)new char [headLen+head.len];
memcpy(res,data.data(),headLen+head.len);
responses.emplace_back(res);
//解包完成后清除该部分数据
data.erase(data.begin(),data.begin()+headLen+res->len);
}while(data.size()>=headLen);
return responses;
}
有解包就要封包,这种模式下的数据如何封包并发送呢?一般情况下,对于客户端来说,只需要直接实例化一个Request,然后调用send函数直接发送。
但是在使用了柔性数组(上文的char body[]
)之后,需要修改一下实例化的方式,下面的代码展示了发送一个登录请求时的操作。
auto *req = (Request *) new char[sizeof(Request) + sizeof(LoginInfo)];
req->cmd = Cmd::Login;
req->len = sizeof(loginInfo);
memcpy((char *) req + sizeof(Request), &loginInfo, req->len);
if (send(fd, (char *) req, sizeof(Request) + req->len, 0) <= 0) {
return -1;
}
注意到上述代码中有LoginInfo
类型,这个类型的作用是什么呢?在使用柔性数组进行数据传输时,Request的char body[]
数据在解包的时候并不知道具体是什么,在处理命令请求的时候根据约定才知道该数据具体类型,所以LoginInfo
是传输数据的一种,类似的还定义了以下几种:
struct LoginInfo{
char username[64];
char password[64];
};
struct ListGroupInfoReq{
char user[64];
};
struct ListGroupInfoRes{
char group[32][64];
};
struct Message{
char group[64];
char from[64];
char message[1024];
};
客户端逻辑
首先,建立连接
// 服务端地址
struct sockaddr_in serv_addr{};
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(1234);
//创建socket并连接到服务端
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (connect(fd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
std::cerr << "connect failed." << std::endl;
return -1;
}
连接建立后,创建一个线程接收数据,处理服务端传回的Response。
//连接建立后,创建一个线程接收数据
std::thread recvThread([&]() {
std::vector<char> buffer;
char recvBuf[1024];
memset(recvBuf, 0, sizeof(recvBuf));
while (!stop) {
ssize_t bytes_len = recv(fd, recvBuf, sizeof(recvBuf), 0);
if (bytes_len == 0) {
std::cout << "server closed." << std::endl;
break;
} else if (bytes_len < 0) {
std::cout << "recv error. exit!" << std::endl;
break;
} else {
auto buffSize = buffer.size();
buffer.resize(buffSize + bytes_len);
memcpy(buffer.data() + buffSize, recvBuf, bytes_len);
auto responses = unpackResponse(buffer);
for(auto & response:responses){
// 处理被动接受的的消息
if(response->cmd==Cmd::RecvMessage){
Message message{};
memcpy(&message,response->body,sizeof (message));
std::cout<<"[M]"<<message.from<<":"<<message.group<<","<<message.message<<std::endl;
continue;
}
// 其他消息不处理,需要交给主动调用出同步等待后处理,如何实现?
}
}
}
stop = true;
});
这种处于后台的数据处理方式,作为主线程如何在发送一个请求后等待请求结果呢?这里采用了条件变量的方式来实现,上述的代码改成这样:
std::vector<Response*> responseList;
std::mutex mutexResponse;
std::condition_variable cvResponseAvailable;
//连接建立后,创建一个线程接收数据
std::thread recvThread([&]() {
std::vector<char> buffer;
char recvBuf[1024];
memset(recvBuf, 0, sizeof(recvBuf));
while (!stop) {
ssize_t bytes_len = recv(fd, recvBuf, sizeof(recvBuf), 0);
if (bytes_len == 0) {
std::cout << "server closed." << std::endl;
break;
} else if (bytes_len < 0) {
std::cout << "recv error. exit!" << std::endl;
break;
} else {
auto buffSize = buffer.size();
buffer.resize(buffSize + bytes_len);
memcpy(buffer.data() + buffSize, recvBuf, bytes_len);
auto responses = unpackResponse(buffer);
std::unique_lock<std::mutex> lck(mutexResponse);
for(auto & response:responses){
//处理被动接受的的消息
if(response->cmd==Cmd::RecvMessage){
//output message
Message message{};
memcpy(&message,response->body,sizeof (message));
std::cout<<"[M]"<<message.from<<":"<<message.group<<","<<message.message<<std::endl;
continue;
}
responseList.push_back(response);
}
if(!responseList.empty()){
cvResponseAvailable.notify_all();
}
}
}
stop = true;
});
//服务端响应处理函数
auto getResponse=[&](){
Response * response = nullptr;
{
std::unique_lock<std::mutex> l(mutexResponse);
cvResponseAvailable.wait(l, [&] { return !responseList.empty(); });
response = responseList.front();
responseList.erase(responseList.begin());
}
return response;
};
在发送完一个请求后如果需要等待该请求的结果,只需要使用getResponse
函数获取一个请求。这么做比较简易,但存在显而易见的问题:获取到的响应结果对应的请求并不是自己发送的请求。
一个改进的思路是,对请求进行唯一标识。
服务端逻辑
服务端启动后,先初始化数据,然后开始监听。当有心得客户端请求来时,加入select的fdset,开始服务该客户端。
初始化数据
auto * db = new service::Db;
启动服务器,监听请求
//初始化服务器
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(1234);
int srvFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (bind(srvFd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
std::cout << "bind failed!" << std::endl;
return -1;
}
listen(srvFd, 5);
在主进程中服务客户端
//客户端buffer
std::map<int,std::vector<char>> clientBufferMap;
//使用select
fd_set readFdSet;
fd_set tmpReadFdSet;
FD_ZERO(&readFdSet);
FD_SET(srvFd, &readFdSet);
int ret;
while (!stop) {
tmpReadFdSet = readFdSet;
ret = select(FD_SETSIZE, &tmpReadFdSet, nullptr, nullptr, nullptr);
if (ret == 0) {
std::cerr << "time out." << std::endl;
break;
} else if (ret < 0) {
std::cerr << "select error." << std::endl;
break;
}
// 轮询处理读事件
for (int fd = 0; fd < FD_SETSIZE; fd++) {
if (!FD_ISSET(fd, &tmpReadFdSet)) {
continue;
}
// 处理server读事件,接受请求
if (fd == srvFd)
{
struct sockaddr_in client_address{};
socklen_t client_len = sizeof(client_address);
int cfd = accept(fd, (struct sockaddr *) &client_address, &client_len);
FD_SET(cfd, &readFdSet);
std::cout << "client[" << cfd << "]connected." << std::endl;
clientBufferMap[fd]={};
}
else //处理客户端事件
{
//接收数据
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
ssize_t bytes_len = recv(fd, buffer, sizeof(buffer), 0);
//数据处理
if (bytes_len == 0) {
//TCP中数据长度为0,表示断开连接
std::cout << "client " << fd << " closed." << std::endl;
FD_CLR(fd, &readFdSet);
clientBufferMap.erase(fd);
break;
} else if (bytes_len < 0) {
std::cerr << "client " << fd << " recv error." << std::endl;
FD_CLR(fd, &readFdSet);
clientBufferMap.erase(fd);
break;
} else {
//拷贝buffer到map中
std::vector<char> &buff = clientBufferMap[fd];
auto buffSize = buff.size();
buff.resize(buffSize+bytes_len);
memcpy(buff.data()+buffSize,buffer,bytes_len);
// 解包
auto requests = unpackRequest(buff);
// 处理 requests
}
}
}
}
服务过程中,对客户端的请求进行响应,采用一问一答方式;当客户端发送群聊消息时,将该消息推送到该群组内的所有成员客户端。
//接上段解包
auto requests = unpackRequest(buff);
Response * response = nullptr;
for(auto req : requests){
//处理请求并构造response
switch (req->cmd) {
case Cmd::Login:
response = service::dealLogin(req,db,fd);
break;
case Cmd::ListGroup:
response = service::dealListGroup(req,db);
break;
case Cmd::SendMessage:
response = service::dealSendMessage(req,db);
break;
default:break;
}
if(response){
//每个请求有一个响应结果,发送请求结果
send(fd,response,sizeof(Response)+response->len,0);
//如果是发送消息的命令,需要将消息推送到关联的客户端
if(response->cmd==Cmd::SendMessage){
//推送消息
auto messageResponse = (Response*)new char[sizeof (Response)+req->len];
messageResponse->code = ErrorCode::Success;
messageResponse->cmd = Cmd::RecvMessage;
messageResponse->len = req->len;
memcpy(messageResponse->body,req->body,req->len);
//send message to group
Message message{};
memcpy(&message,(char *)req->body,sizeof(message));
//查找制定群组中在线的客户端
for(auto & group:db->groupList){
if(group.name==message.group){
for(auto & user: group.userArray){
if(user.fd>0){
std::cout<<"send to "<<user.name<<":"<<message.message<<std::endl;
send(user.fd,messageResponse,sizeof(Response)+messageResponse->len,0);
}
}
}
}
}
delete response;
response = nullptr;
}
delete req;
}
相关代码
协议
//common/chat_protocol.h
#ifndef BASIC_DEMO_CHAT_PROTOCOL_H
#define BASIC_DEMO_CHAT_PROTOCOL_H
#include <iostream>
#include <list>
namespace chat{
struct User{
std::string name; //用户昵称
std::string ip; //ip地址
std::string usr; //登录用户名
std::string pwd; //登录密码
unsigned short port;//端口号
bool online; //在线状态
int fd; //socket描述符,方便直接发送消息
};
struct Group{
std::string name; //群组名称
std::list<User> userArray; //该群组用户列表
};
//命令字
enum Cmd:unsigned int{
None,
Login, //登录
Logout, //登出
ListGroup, //列出所有群组
JoinGroup, //加入群组(未实现)
LeaveGroup, //离开群组(未实现)
SendMessage, //发送消息
RecvMessage //主动接收消息
};
//请求结构
struct Request{
Cmd cmd; //命令字
unsigned int len; //数据长度
char body[]; //数据体
};
//响应码
enum ErrorCode{
Unknown,
Success,
Failure
};
//响应结果
struct Response{
Cmd cmd; //命令字
ErrorCode code; //响应码
int len; //数据长度
char body[0]; //数据体
};
//数据体的基本结构
struct LoginInfo{
char username[64];
char password[64];
};
struct ListGroupInfoReq{
char user[64];
};
struct ListGroupInfoRes{
char group[32][64];
};
struct Message{
char group[64];
char from[64];
char message[1024];
};
// 请求解包
std::vector<Request*> unpackRequest(std::vector<char> & data){
std::vector<Request*> requests;
int headLen = sizeof (Request);
do{
//解头部
if(data.size()<headLen){
break;
}
Request head{};
memcpy(&head,data.data(),sizeof (Request));
if(data.size()-headLen<head.len){
break;
}
//解body
auto * req = (Request *)new char [headLen+head.len];
memcpy(req,data.data(),headLen+head.len);
requests.emplace_back(req);
//解包完成后清除该部分数据
data.erase(data.begin(),data.begin()+headLen+req->len);
}while(data.size()>=headLen);
return requests;
}
// 响应解包
std::vector<Response*> unpackResponse(std::vector<char> & data){
std::vector<Response*> responses;
int headLen = sizeof (Response);
do{
//解头部
if(data.size()<headLen){
break;
}
Response head{};
memcpy(&head,data.data(),sizeof (Response));
if(data.size()-headLen<head.len){
break;
}
//解body
auto * res = (Response *)new char [headLen+head.len];
memcpy(res,data.data(),headLen+head.len);
responses.emplace_back(res);
//解包完成后清除该部分数据
data.erase(data.begin(),data.begin()+headLen+res->len);
}while(data.size()>=headLen);
return responses;
}
}
#endif //BASIC_DEMO_CHAT_PROTOCOL_H
客户端
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <iostream>
#include "thread"
#include "../common/chat_protocol.h"
#include "list"
using namespace chat;
int main() {
std::atomic_bool stop = false;
// 服务端地址
struct sockaddr_in serv_addr{};
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(1234);
//创建socket并连接到服务端
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (connect(fd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
std::cerr << "connect failed." << std::endl;
return -1;
}
std::vector<Response*> responseList;
std::mutex mutexResponse;
std::condition_variable cvResponseAvailable;
//连接建立后,创建一个线程接收数据
std::thread recvThread([&]() {
std::vector<char> buffer;
char recvBuf[1024];
memset(recvBuf, 0, sizeof(recvBuf));
while (!stop) {
ssize_t bytes_len = recv(fd, recvBuf, sizeof(recvBuf), 0);
if (bytes_len == 0) {
std::cout << "server closed." << std::endl;
break;
} else if (bytes_len < 0) {
std::cout << "recv error. exit!" << std::endl;
break;
} else {
auto buffSize = buffer.size();
buffer.resize(buffSize + bytes_len);
memcpy(buffer.data() + buffSize, recvBuf, bytes_len);
auto responses = unpackResponse(buffer);
std::unique_lock<std::mutex> lck(mutexResponse);
for(auto & response:responses){
//处理被动接受的的消息
if(response->cmd==Cmd::RecvMessage){
//output message
Message message{};
memcpy(&message,response->body,sizeof (message));
std::cout<<"[M]"<<message.from<<":"<<message.group<<","<<message.message<<std::endl;
continue;
}
responseList.push_back(response);
}
if(!responseList.empty()){
cvResponseAvailable.notify_all();
}
}
}
stop = true;
});
//服务端响应处理函数
auto getResponse=[&](){
Response * response = nullptr;
{
std::unique_lock<std::mutex> l(mutexResponse);
cvResponseAvailable.wait(l, [&] { return !responseList.empty(); });
response = responseList.front();
responseList.erase(responseList.begin());
}
return response;
};
//发送登录命令
LoginInfo loginInfo{};
bool loginSuccess = false;
while (!loginSuccess){
std::cout << "Input you user name:" << std::endl;
std::cin >> loginInfo.username;
std::cout << "Input you password:" << std::endl;
std::cin >> loginInfo.password;
auto *req = (Request *) new char[8 + sizeof(loginInfo)];
req->cmd = Cmd::Login;
req->len = sizeof(loginInfo);//128
memcpy((char *) req + 8, &loginInfo, req->len);
if (send(fd, (char *) req, 8 + req->len, 0) <= 0) {
std::cout << "send login cmd failed!" << std::endl;
return -1;
}
//等待请求响应
auto response = getResponse();
if(response->cmd==Cmd::Login){
if(response->code==ErrorCode::Success){
std::cout<<"login succeed!"<<std::endl;
loginSuccess = true;
}else{
std::cout<<"login failed!"<<std::endl;
}
}else if(response->cmd==Cmd::RecvMessage){
if(response->code==ErrorCode::Success){
std::cout<<"recv message succeed!"<<std::endl;
Message message{};
memcpy(&message,(char *)response->body,sizeof(message));
std::cout<<message.group<<":"<<message.from<<"said:"<<message.message<<std::endl;
}else{
std::cout<<"recv message failed!"<<std::endl;
}
}
}
std::string currentGroup;
//list all group and join one
bool listResponse = false;
while (!listResponse){
ListGroupInfoReq data{};
strcpy(data.user,loginInfo.username);
auto *req = (Request *) new char[sizeof (Request)+ sizeof(ListGroupInfoReq)];
req->cmd = Cmd::ListGroup;
req->len = sizeof(data);
memcpy((char *) req + 8, &data, req->len);
if (send(fd, (char *) req, 8 + req->len, 0) <= 0) {
std::cout << "send list group cmd failed!" << std::endl;
return -1;
}
//等待请求响应
auto response = getResponse();
if(response->cmd==Cmd::ListGroup){
if(response->code==ErrorCode::Success){
std::cout<<"list succeed!"<<std::endl;
listResponse = true;
ListGroupInfoRes res{};
memcpy(&res,response->body,response->len);
int count = 0;
for(int i = 0;i<sizeof(res.group)/sizeof (res.group[0]);i++){
if(res.group[i][0]!=0){
std::cout<<i<<":"<<res.group[i]<<std::endl;
count ++;
}
}
std::cout<<"type the code to choose a group and start chat!"<<std::endl;
int code;
while(true){
std::cin>>code;
if(code<0||code>=count){
continue;
}
break;
}
std::string group = res.group[code];
std::cout<<"Now you entered the group "<<group<<",and you can text message in this group."<<std::endl;
currentGroup = group;
}else{
std::cout<<"list failed!"<<std::endl;
}
}
}
// text message
while(!stop){
Message message{""};
strcpy(message.from,loginInfo.username);
strcpy(message.group,currentGroup.c_str());
std::cin>>message.message;
if(stop||strcmp(message.message,"quit")==0){
break;
}
auto * request = (Request*)new char[sizeof(Request)+sizeof (message)];
request->cmd = Cmd::SendMessage;
request->len = sizeof (message);
memcpy(request->body,&message,sizeof (message));
ssize_t bytes_len = send(fd, request, sizeof(Request)+sizeof (message),0);
if(bytes_len<0)
{
std::cout<<"send error. exit!"<<std::endl;
break;
}
}
stop = true;
recvThread.join();
close(fd);
return 0;
}
服务端
//main.cpp
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <list>
#include <map>
#include <vector>
#include <thread>
std::atomic_bool stop = false;
#include "../common/chat_protocol.h"
#include "service.hpp"
using namespace chat;
int main() {
auto * db = new service::Db;
//初始化服务器
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(1234);
fd_set readFdSet;
fd_set tmpReadFdSet;
FD_ZERO(&readFdSet);
int srvFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (bind(srvFd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
std::cout << "bind failed!" << std::endl;
return -1;
}
listen(srvFd, 5);
std::map<int,std::vector<char>> clientBufferMap;
FD_SET(srvFd, &readFdSet);
int ret;
while (!stop) {
tmpReadFdSet = readFdSet;
ret = select(FD_SETSIZE, &tmpReadFdSet, nullptr, nullptr, nullptr);
if (ret == 0) {
std::cerr << "time out." << std::endl;
break;
} else if (ret < 0) {
std::cerr << "select error." << std::endl;
break;
}
for (int fd = 0; fd < FD_SETSIZE; fd++) {
if (!FD_ISSET(fd, &tmpReadFdSet)) {
continue;
}
if (fd == srvFd) //处理server
{
struct sockaddr_in client_address{};
socklen_t client_len = sizeof(client_address);
int cfd = accept(fd, (struct sockaddr *) &client_address, &client_len);
FD_SET(cfd, &readFdSet);
std::cout << "client[" << cfd << "]connected." << std::endl;
clientBufferMap[fd]={};
} else //处理消息
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
ssize_t bytes_len = recv(fd, buffer, sizeof(buffer), 0);
if (bytes_len == 0) {
std::cout << "client " << fd << " closed." << std::endl;
FD_CLR(fd, &readFdSet);
clientBufferMap.erase(fd);
break;
} else if (bytes_len < 0) {
std::cerr << "client " << fd << " recv error." << std::endl;
FD_CLR(fd, &readFdSet);
clientBufferMap.erase(fd);
break;
} else {
std::vector<char> &buff = clientBufferMap[fd];
auto buffSize = buff.size();
buff.resize(buffSize+bytes_len);
memcpy(buff.data()+buffSize,buffer,bytes_len);
std::cout<<"buff of "<<fd<<","<< buff.size()<<":"<<std::hex;
for(auto & ch : buff){
std::cout<<ch;
}
std::cout<<std::dec<<std::endl;
auto requests = unpackRequest(buff);
Response * response = nullptr;
for(auto req : requests){
switch (req->cmd) {
case Cmd::Login:
response = service::dealLogin(req,db,fd);
break;
case Cmd::ListGroup:
response = service::dealListGroup(req,db);
break;
case Cmd::SendMessage:
response = service::dealSendMessage(req,db);
break;
default:break;
}
if(response){
//发送请求结果
send(fd,response,sizeof(Response)+response->len,0);
if(response->cmd==Cmd::SendMessage){
//推送消息
auto messageResponse = (Response*)new char[sizeof (Response)+req->len];
messageResponse->code = ErrorCode::Success;
messageResponse->cmd = Cmd::RecvMessage;
messageResponse->len = req->len;
memcpy(messageResponse->body,req->body,req->len);
//send message to group
Message message{};
memcpy(&message,(char *)req->body,sizeof(message));
//find group and users in group,send to theme
for(auto & group:db->groupList){
if(group.name==message.group){
for(auto & user: group.userArray){
if(user.fd>0){
std::cout<<"send to "<<user.name<<":"<<message.message<<std::endl;
send(user.fd,messageResponse,sizeof(Response)+messageResponse->len,0);
}
}
}
}
}
delete response;
response = nullptr;
}
delete req;
}
}
}
}
}
close(srvFd);
delete db;
return 0;
}
//service.hpp
#ifndef BASIC_DEMO_SERVICE_HPP
#define BASIC_DEMO_SERVICE_HPP
#include "../common/chat_protocol.h"
namespace service{
using namespace chat;
class Db{
public:
Db(){
//初始化一批用户
User user1{"张三","","user1","123456",0,false,-1};
User user2{"李四","","user2","123456",0,false,-1};
User user3{"王二狗","","user3","123456",0,false,-1};
User user4{"张全蛋","","user4","123456",0,false,-1};
Group group1{"群组1",{user1,user2}};
Group group2{"群组2",{user1,user2,user3}};
groupList = {group1,group2};
userList = {user1,user2,user3,user4};
}
std::list<Group> groupList;
std::list<User> userList;
};
inline Response * dealLogin(const Request * req,Db * db,int fd){
auto * response = new Response{req->cmd,ErrorCode::Unknown,0};
LoginInfo loginInfo{};
memcpy(&loginInfo,(char *)req->body,sizeof(loginInfo));
std::cout<<"request login:user="<<loginInfo.username<<",password="<<loginInfo.password<<std::endl;
auto userIter = std::find_if(db->userList.begin(),db->userList.end(),[&](const User &user){
return user.usr==loginInfo.username;
});
if(userIter==db->userList.end()){
std::cerr<<"user is not registered."<<std::endl;
}
else if(userIter->pwd!=loginInfo.password){
std::cerr<<"password is not correct."<<std::endl;
}else{
std::cout<<"login success."<<std::endl;
response->code = ErrorCode::Success;
userIter->fd = fd;
//遍历group查找user
for(auto &group:db->groupList){
auto gUser = std::find_if(group.userArray.begin(),group.userArray.end(),[&](const User &user){
return user.usr==loginInfo.username;
});
if(gUser==group.userArray.end()){
continue;
}
gUser->fd = fd;
}
}
return response;
}
inline Response * dealListGroup(const Request * req,Db * db){
ListGroupInfoReq reqData{};
memcpy(&reqData,(char *)req->body,sizeof(reqData));
std::cout<<"request list group of user "<<reqData.user<<std::endl;
ListGroupInfoRes resData{};
int groupIndex = 0;
for(auto & group :db->groupList){
std::cout<<"searching in group "<<group.name<<std::endl;
bool find = false;
for(auto & user:group.userArray){
std::cout<<"searching in users "<<user.usr<<" is equal to "<<reqData.user<<std::endl;
if(user.usr==reqData.user){
find = true;
break;
}
}
if(find) {
strcpy(resData.group[groupIndex], group.name.c_str());
std::cout<<"find:"<<resData.group[groupIndex];
groupIndex ++;
}
}
auto response = (Response*)new char[sizeof (Response)+sizeof (resData)];
response->code = ErrorCode::Success;
response->cmd = Cmd::ListGroup;
response->len = sizeof (resData);
memcpy(response->body,&resData,response->len);
return response;
}
inline Response * dealSendMessage(const Request * req,Db * db){
auto response = (Response*)new char[sizeof (Response)];
response->code = ErrorCode::Success;
response->cmd = Cmd::SendMessage;
response->len = 0;
return response;
}
}
#endif //BASIC_DEMO_SERVICE_HPP
标签:std,群聊,自定义,req,通信协议,len,char,sizeof,response
From: https://www.cnblogs.com/pengpengda/p/18181820