基于KCP的聊天室项目--(服务器端)

基于KCP的聊天室项目代码的主要逻辑

其中kcp的讲解在kcp协议介绍与解析

一、主要逻辑

封装一个ChatServer类,该类保存服务器端正在进行的会话的指针,并实现消息处理和会话处理业务。
ChatServer类的run函数定义一个主循环,该主循环中包含两个线程,一个线程循环检测现有的会话是否失效。另一个线程循环接收消息,根据消息中的会话id找到或者添加新会话,然后判断是保活信息还是聊天信息并进行相应处理

二、相关结构

主程序中建立一个ChatServer类对象并设置它的kcp属性,然后调用run方法进入主循环

std::string ip = argv[1];
uint16_t port = atoi(argv[2]);

KcpOpt opt;
opt.conv = 0;
opt.is_server = true;
opt.keep_alive_timeout = 5000; // 超过5秒没有收到客户端的信息则认为其断开
ChatServer server(opt, ip, port);

server.Run();

其中KcpOpt是存储KCP的设置属性的结构,我们先看它的内容

class KcpOpt {
  public:
    bool is_server = true;
    int64_t keep_alive_timeout = 5000; 	// 超时时间
    									// 如果服务器端超过这个时间没接收到新的数据就移除这个会话
    									// 如果客户端超过这个时间没接收到新的数据就发送保活数据
    uint32_t conv = 0;       // 会话id,该设置对服务端有效,服务端主要解析client的conv并创建同样的会话
    int sndwnd = 32;         // 默认发送窗口
    int rcvwnd = 128;        // 默认接收窗口
    int nodelay = 0;         // 无延迟是否开启 0未开启,1/2开启
    int interval = 10;       // 调度间隔时间, 默认10毫秒
    int resend = 0;          // 快速重传指标,0未开启,1/2开启
    int nc = 0;              // 流控 0开启,1关闭
};

ChatServer类是对TCPServer类的扩展,我们注意TCPServer类主要的成员变量

class KcpServer {
    KcpOpt kcp_opt_;			//存储KCP的属性
    UdpSocket::ptr socket_;		//指向UdpSocket变量的共享指针
	
	using SessionMap = std::unordered_map<uint32_t, KcpSession::ptr>;
    SessionMap sessions_;		//保存正在通信会话的指针map
    
    std::vector<char> buf_;		//缓冲区
};

ChatServer类实现新的客户端逻辑接入和断开,以及与客户端收发信息等业务

class ChatServer : public KcpServer {
  public:
    using KcpServer::KcpServer;
    // 接收客户端发送过来的消息
    virtual void HandleMessage(const KcpSession::ptr &session,
                               const std::string &msg);  //收到客户端发来的信息的处理: 通知其他人
    void HandleConnection(const KcpSession::ptr &session); //检测到新的客户端: 添加新的会话并通知其它人
    void HandleClose(const KcpSession::ptr &session);	//检测到会话断开后的后续处理: 删除该会话以及通知其它人
    void Notify(const std::string &str); 				//向所有保存的会话发送信息
  public:
    using Lock = std::unique_lock<std::mutex>;
  private:
    std::unordered_set<KcpSession::ptr> users_;  		// 会话保存
    std::mutex mtx_;
};

KcpSession类的主要成员变量

class KcpSession {
  public:
    using ptr = std::shared_ptr<KcpSession>;
  private:
    KcpOpt kcp_opt_;			//该会话的属性
    uint32_t conv_;				//会话的标志(id)
    sockaddr_in addr_;			//会话对象的地址、端口
    UdpSocket::ptr socket_;		//udp的封装,用来接收发送信息
    ikcpcb *kcp_;				//kcp控制块,用于控制包的发送,接收,重传等操作
    int64_t recv_latest_time = 0;	//上次接收的时间
    int64_t send_latest_time = 0;	//上次发送的时间
};

其中UdpSocket是对UDP通信的封装

class UdpSocket // udp的封装
{
//封装的接口略
private:
        std::string ip_;		//服务器地址
        uint16_t    port_;		//服务器端口
        int         fd_;		//服务器监听的socket
        int         family_;	//服务器使用的协议
        sockaddr_in addr_;		//目标对象的地址、端口等
};

三、主循环

我们来看主循环,主要包含两个线程

void KcpServer::Run() {
    std::thread t([this]() {
        while (true) {
            usleep(10);
            Update();
        }
    });

    do {
        sockaddr_in addr;
        int len = socket_->RecvFrom(buf_.data(), body_size, addr); // 采用block的方式 读取到udp
                                                             //  if (len != -1)
        // trace("recvfrom:len = ", len);
        if (!Check(len)) // 检测是否有数据可以读取 header 24字节
            continue;

        uint32_t conv = GetConv(buf_.data());
        {
            Lock lock(mtx_); // 加锁
            //   TRACE("conv:", conv);
            KcpSession::ptr session = GetSession(conv, addr);

            HandleSession(session, len);
        }

    } while (1);
    t.join(); // 等待线程退出
}

线程1循环检查是否有失效的会话,如果超时就移除会话和指向它的指针

void KcpServer::Update() {
    Lock lock(mtx_);   // session的管理
    for (auto it = sessions_.begin(); it != sessions_.end();) {
        if (!it->second->Update(iclock64())) {
            ++it;
            continue;
        }
        HandleClose(it->second);
        it = sessions_.erase(it);
    }
}

线程2循环阻塞式接收给它的报文,根据会话id(conv)找到会话。
如果是新会话id就新建会话,否则就更新会话的客户端地址和端口。

KcpSession::ptr KcpServer::GetSession(uint32_t conv, const sockaddr_in &addr) {
    auto it = sessions_.find(conv);

    if (it == sessions_.end())
        sessions_[conv] = NewSession(conv, addr);

    KcpSession::ptr session = sessions_[conv];

    const sockaddr_in &session_addr = session->GetAddr();

    if (session_addr.sin_port != addr.sin_port ||
        session_addr.sin_addr.s_addr != addr.sin_addr.s_addr) {
        session->SetAddr(addr);  // 保存的客户端的地址 ip + port
    }

    return session;
}

然后判断是保活信息还是聊天信息
如果是保活信息就更新该会话的上次接收时间
如果是聊天信息就调用信息处理

void KcpServer::HandleSession(const KcpSession::ptr &session, int length) {
    int ret = session->Input(buf_.data(), length);

    if (ret != 0) {
        TRACE("Input error = ", ret);
        return;
    }

    do {
        int len = session->Recv(buf_.data(), body_size); // 读取用户数据

        if (len == -3) {
            TRACE("body size too small");
            exit(-1);
        }

        if (len <= 0)
            break;

        // 先检测是不是保活命令
        if (memcmp(buf_.data(), KCP_KEEP_ALIVE_CMD,
                   sizeof(KCP_KEEP_ALIVE_CMD)) == 0) {
            // TRACE("recv conv: ", session->conv(), " keep alive cmd");
            session->SetKeepAlive(iclock64()); // 通过带宽换cpu效率
            continue;
        }

        std::string msg(buf_.data(), buf_.data() + len);
        //   TRACE(msg);
        HandleMessage(session, msg); // 当前线程读取, 调用业务子类处理业务
    } while (1);
}
  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
KCP(Kuai Connection Protocol)是一种可靠的快速传输协议,适用于网络传输场景。在net项目中使用KCP可以带来如下好处: 1. 提高传输速度:KCP采用了传统TCP协议不具备的一些优势,如平滑的拥塞控制、快速重传和数据抓取等技术,可以在较高丢包率的网络环境下保持较高的传输速度,有效解决了TCP协议的一些传输效率和延迟问题。 2. 增强可靠性:KCP使用了UDP协议进行数据传输,但它在UDP基础上增加了一些可靠性机制,如数据确认、重传机制、窗口控制等,能够在不可靠的网络环境中保证数据的可靠传输,避免丢包和乱序等问题。 3. 降低延迟:KCP具有较低的传输延迟,能够减少发送数据和接收数据之间的时间差,提高数据传输的实时性,适合对延迟要求较高的应用场景,如在线游戏、语音通话等。 4. 自适应网络KCP网络环境发生变化时具有较好的自适应性,能够根据网络质量的变化来动态调整发送和接收的数据量,保持较稳定的传输效果,适合在具有不稳定网络环境的情况下使用。 5. 简单易用:KCP的实现相对简单,使用KCP协议进行网络传输的代码量较小,易于集成和使用,适合在开发网络传输模块时快速实现。 总之,net项目使用KCP可以带来优秀的传输性能、可靠性和较低的延迟,适用于各种网络传输需求,尤其适合在不稳定网络环境中保持较好的网络传输效果。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值