首页 > 其他分享 >自定义协议通信协议实现简易群聊

自定义协议通信协议实现简易群聊

时间:2024-05-09 12:02:40浏览次数:21  
标签:std 群聊 自定义 req 通信协议 len char sizeof response

基础需求

简易版聊天室,仅为演示自定义协议,所以只添加了登录登出功能。

代码部分相当粗糙,很多场景没有进行细致考虑,仅展现了一个思路。

首先进行一下基本流程分析

  • 服务端启动以后,监听某个地址和端口,接收新的客户端连接。
  • 连接建立以后,客户端发送登录请求,服务端进行校验并返回请求的结果。
  • 如果验证通过,服务端将客户端接入聊天室并提供服务
  • 客户端此时可以发送消息以及看到其他客户端的聊天消息
  • 当客户端退出时,发起登出请求。

由上可以得出客户端请求类型

  • 登录请求
  • 登出请求
  • 查看群组
  • 加入群组
  • 退出群组
  • 发送消息
  • 接收消息

协议制定

以整个服务端为一个聊天室,为了支持登录登出功能,需要一个数据结构来记录当前聊天室内的用户以及状态(在线、离线),由于不涉及私聊功能以及聊天记录功能,我们设计出如下的数据结构:

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

相关文章

  • 微信小程序使用微信云托管添加自定义域名并转发到pexels.com
    背景:我要在小程序上显示pexels.com上的图片,然后我得先把pexels.com的域名添加到小程序的request合法域名中,但是pexels.com是国外的,在国内没有备案所以添加不了。解决方案就是:用一个已经备案好的域名进行转发,转发的服务器我选择的是微信云托管,备案好的域名还需要ssl,没有的话本文会......
  • 自定义Behavior
    自定义Behavior实现功能在鼠标滚轮滚动时,ComboBox的SelectIndex也实现递增和递减CodepublicclassComboxMouseWheelBehavior:Behavior<ComboBox>{protectedoverridevoidOnAttached(){AssociatedObject.MouseWheel+=ComboxMouseWheel;}......
  • 自定义一个radio
    html<viewclass="radio-out":style="{'border-color':selectFlag?'blue':''}"><viewclass="radio-in":style="{'background-color':selectFlag?'blue':'......
  • mfc自定义控件的自动布局
    **CBRS_ALIGN_RIGHT是MFC(MicrosoftFoundationClass)中的一个标志,用于指示控件条可以停靠在框架窗口的客户区域右侧**。 在MFC中,窗口布局和控件的管理是一个重要的功能,尤其是在涉及到用户界面设计时。MFC提供了一套完整的机制来允许开发者创建和管理应用程序的界面,包括控......
  • C#多选下拉菜单自定义控件
    C#在winform项目中多选下拉菜单自定义控件。由 ComboBox和 CheckedListBox组合形成。效果: 自定义控件代码MultiComboBox.csusingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Drawing;usingSystem.Data;usingSystem......
  • C++基础-如何引入第三方静态库、动态库或自定义库 摘自 https://blog.csdn.net/u01310
    C++无论是内置库还是第三方库,都需要自己手动进行查找、配置、引入等工作。本文即是帮助完成C++项目对于库、框架如何完成依赖引入达成可调用的目的,重点讲述开发工具VisualStudio中的操作静态库(.lib)静态库引入适用用于大部分无开源的第三方库,开发者不需要关心库的具体实现如何,......
  • 自定义单链表(非循环)的基本接口函数
    文件描述及头文件包含/********************************************************************* 文件名称: 单链表(非循环)的基本接口程序* 文件作者:[email protected]* 创建日期:2024/05/07* 文件功能:对单链表的增删改查功能的定义* 注意事项:No......
  • 自定义单链表(非循环)反转的基本函数接口
    题干structListNode*ReverseList(structListNode*head){if(head==NULL||head->next==NULL){returnhead;}else{structListNode*Phead=head;structListNode*temp=head->next;Phead->next=NULL;......
  • shell 脚本中使用自定义的alias别名
    摘自:https://blog.csdn.net/cscrazybing/article/details/41285287alias,假名,别名,bash的一个内建命令,用来给常用的较长的命令定义个简短的名称。alias命令的基本格式为alias[word[='command']],[]内为可选项。定义word为command的别名。若=’command’部分省略,则输出word......
  • ECharts自定义提示框浮层内容
    因为提示框内容支持字符串模板和回调函数两种形式字符串模板模板变量有{a},{b},{c},{d},{e},分别表示系列名,数据名,数据值等等,但是trigger属性为axis的时候它数据条就很多了,就可以用{a0},{a1},{a2}这样子去拿数据跟数组下标一样(官网有详细示例)示例:在`option`中的`tooltip`里边写......