Linux C/C++ 学习日记(28):KCP协议(四):如何实现更复杂的业务:将连接状态的管理进行封装,用户只需实现发送、接收、断开的处理逻辑。

注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。

大家看到上一篇文章的示例会觉得代码结构混乱,使用起来很复杂,又要发送心跳,又要检查,客户端发送信息也不知道有没有连接上等等之类的。

为了使用起来更加方便,我们决定将连接封装起来(就像之前介绍reactor那样),由于要定义的变量多,为了代码看起来结构更加清晰。如果只用c的话,后面修改起来太复杂(虽然也可以实现),这里我选择使用C++的类,同时方便进行继承和扩展

一、封装三个类

1. KcpOpt

项目 说明
类名 KcpOpt
核心功能 存储和管理 KCP 协议的配置参数,作为 KCP 会话的 "配置选项容器"
关键成员 / 参数 - is_server:标识是否为服务器端 - server_keep_alive_timeout/client_keep_alive_timeout:服务器 / 客户端心跳超时时间(毫秒) - conv:KCP 会话唯一标识(32 位整数) - sndwnd/rcvwnd:KCP 发送 / 接收窗口大小 - nodelay/interval/resend/nc:KCP 底层参数(控制低延迟模式、超时重传间隔等)
作用 集中管理 KCP 会话的初始化参数,为KcpSession提供配置依据,简化参数配置流程

2. UdpSocket

项目 说明
类名 UdpSocket
核心功能 封装 UDP 套接字的底层操作,提供 UDP 数据传输的基础能力
关键成员 / 方法 - fd_:UDP 套接字文件描述符 - myaddr_:本地 IP 和端口信息(sockaddr_in) - SendTo(...):向指定目标地址发送 UDP 数据 - RecvFrom(...):从指定源地址接收 UDP 数据 - Bind():绑定本地 IP 和端口 - GetAddrString():获取本地地址的字符串表示(如 "192.168.248.130:8888") -SetNoblock():开启非阻塞模式 -SetBlock():关闭非阻塞模式 -IsBlock():判断是否为阻塞模式
作用 作为 KCP 协议的底层传输载体,屏蔽 UDP 套接字的系统调用细节(如socket/sendto/recvfrom),为KcpSession提供可靠的 UDP 数据收发能力

3. KcpSession

项目 说明
类名 KcpSession
核心功能 封装单个 KCP 会话的完整生命周期管理,实现 KCP 协议的核心逻辑
关键成员 / 方法 - kcp_:KCP 协议实例(ikcpcb*) - socket_:关联的UdpSocket实例(用于 UDP 传输) - oppaddr_:对端 IP 和端口信息(sockaddr_in) - state_ : 判断连接状态 - Update(...):定时更新 KCP 状态,检测心跳超时(服务器端超时断开,客户端定时发心跳) - Send(...)/Recv(...):通过 KCP 发送 / 接收可靠数据(内部调用ikcp_send/ikcp_recv) -SendTo():发送UDP包 -Flush():对标ikcp_flush() - Input(...):将接收到的 UDP 数据注入 KCP 解析 - CheckSpecial(...):处理控制命令(如连接请求 / 响应、断开通知、心跳) -GetOppAddrToString():获取对方的ip和port字符串 -SetOppAddr():修改对方的addr(用于conv对应的addr改变时使用) - Connect(): 发起连接
作用 管理单个 KCP 会话的完整逻辑(协议交互、数据可靠传输、连接保活与断开),作为上层业务与底层 UDP/KCP 协议之间的桥梁

二、为什么这么设计?怎么想到的?

一句话从功能和简化代码出发:上面定义了三个类,事实上就只有一个KcpSession类,另外2个类是为KcpSession服务的。换句话说是在编写KcpSession过程中为了简化代码才定义了另外两个类!!!

从KcpSession的要实现的功能出发,我们就能理解为什么这么设计了:

  1. 为了方便的初始化KcpSession, 我们定义了KcpOpt类

  2. 为了实现UDP包的发送和接收,根据sendto和recvfrom所需的参数,我们知道KcpSession必须要有己方的UdpSocket_fd,和对方的addr,如果fd作为服务器需要绑定地址的话,我们还需知道己方的addr。

为了简化代码量:我们封装一个UdpSocket类,然后在KcpSession中定义一个指向UdpSocket类的指针,就可以获取所有关于己方socket的各种信息了,同时还能调用UDP的各种接口!!!

  1. 仿照上一篇博文的示例我们知道KcpSession必须实现数据的发送和读取,则不难想到:

Send: 对标ikcp_send

Recv: 对标ikcp_recv

Input : 对标ikcp_input

SendTo : 对标output中UDP包的发送 (output中传入的user必须知道己方fd和对方addr,KcpSession就能实现)

Update:对标ikcp_update, 同时我们还额外实现了心跳检测功能。

Flush: 对标ikcp_flush

CheckSpecial:用于检测的特殊命令,比如发起连接,同意连接,断开连接,心跳检测等,随自己拓展

Connect:客户端向服务器发起连接请求,接收到回应后,判定逻辑连接建立

总的来说:

类名 核心职责 遵循的设计原则 设计原因与具体思路
KcpOpt KCP 协议配置参数的集中管理 单一职责原则 1. KCP 依赖大量参数(如conv、窗口大小、超时时间等),分散管理会导致KcpSession职责过重,不符合单一职责; 2. 集中管理实现 "配置与逻辑分离",初始化KcpSession时只需传入KcpOpt对象,参数修改无需改动KcpSession核心逻辑; 3. 可作为配置模板复用,简化批量会话初始化流程。
UdpSocket UDP 底层传输操作的封装 封装性、低耦合 1. UDP 底层操作(如socketsendto等)涉及系统 API,细节复杂且平台相关,直接嵌入KcpSession会导致职责混乱、耦合度高; 2. 封装为类后,向KcpSession提供简洁接口,隐藏底层细节,实现 "传输层与协议逻辑分离"; 3. 智能指针设计支持多会话共享 UDP 套接字,提升资源利用率。
KcpSession KCP 会话的全生命周期管理 高内聚、线程安全、单一职责 1. 集中处理 KCP 核心逻辑(数据可靠传输、心跳保活、连接状态维护),这些逻辑紧密关联,适合高内聚封装; 2. 封装ikcp库 API(如ikcp_send/ikcp_recv),降低上层使用门槛; 3. 通过Update方法收口定时维护逻辑,CheckSpecial处理控制命令,分离业务数据与控制交互; 4. 用mutex保证多线程安全,避免并发操作错乱。

三、代码

1. kcp_session.hpp

cpp 复制代码
#ifndef __KCP_SESSION_HPP__
#define __KCP_SESSION_HPP__

#include "ikcp.h"
#include <cstdint>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <iostream>
#include <sstream>
#include <memory>
#include <unistd.h>
#include <cstring>
#include <unordered_map>
#include <mutex>
#include <thread>
#include <fcntl.h>

#define KEEP_ALIVE_CMD "KEEP_ALIVE_CMD"
#define CONNECTION_ASK "CONNECTION_ASK"
#define CONNECTION_RES "CONNECTION_RES"
#define CONNECTION_OFF "CONNECTION_OFF"
#define KCP_HEADER 24
#define CONNECT_TIME_OUT 1000000 // 1s中没连接上就判定超时
#define Server_ip "192.168.248.130"
#define Server_port 8888
#define BUFFER_LENGTH 1024

#define TRACE(...) trace("[Log: ", __func__, ":", __LINE__, "] :", __VA_ARGS__);
template <typename T, typename... Args>
inline void trace(T &&tmp, Args &&...args)
{
    // constexpr if:编译期判断剩余参数个数,避免运行期分支开销
    if constexpr (sizeof...(args) > 0)
    {
        // 若有剩余参数:打印当前参数+空格,递归处理下一个参数
        std::cout << tmp << " ";
        // 完美转发剩余参数(保持参数原始值类别,左值仍为左值,右值仍为右值)
        trace(std::forward<Args>(args)...);
    }
    else
    {
        // 若无剩余参数:打印当前参数+换行(结束递归,避免末尾空格)
        std::cout << tmp << std::endl;
    }
}

int udp_output(const char *buf, int len, ikcpcb *kcp, void *user);

class KcpOpt
{

public:
    bool is_server = true;
    int64_t server_keep_alive_timeout = 5000; // 定义64位才不会超出,注意,kcp中定义uint32_t,是因为取得是跟0比较的插值,我们这里取得是跟非0比较的插值
    int64_t client_keep_alive_timeout = 4000; // 设置的比服务器快一点,防止延迟导致断开连接
    uint32_t conv = 0;
    int sndwnd = 32;
    int rcvwnd = 128;
    int nodelay = 0;
    int interval = 10;
    int resend = 0;
    int nc = 0;
};

// 获取当前毫秒时间
int64_t current_ms()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

// 存放本端的地址
class UdpSocket
{
public:
    using ptr = std::shared_ptr<UdpSocket>;

    UdpSocket(const char *ip = Server_ip, uint16_t port = Server_port, int domain = AF_INET)
    {
        fd_ = socket(domain, SOCK_DGRAM, 0);
        myaddr_.sin_family = AF_INET;
        myaddr_.sin_addr.s_addr = inet_addr(ip);
        myaddr_.sin_port = htons(port);

        TRACE("UDPSocket");
    }

    ~UdpSocket()
    {
        close(fd_);
    }

    int SendTo(const void *buffer, int len, sockaddr_in &oppaddr)
    {
        int ret = sendto(fd_, buffer, len, 0, (struct sockaddr *)&oppaddr, sizeof(struct sockaddr));
        return ret;
    }

    int RecvFrom(void *buffer, int len, sockaddr_in &oppaddr)
    {
        socklen_t socklen = sizeof(oppaddr);
        int ret = recvfrom(fd_, buffer, len, 0, (struct sockaddr *)&oppaddr, &socklen);
        return ret;
    }

    int Bind()
    {
        bind(fd_, (struct sockaddr *)&myaddr_, sizeof(struct sockaddr));
        return 0;
    }

    std::string GetAddrString()
    {
        std::stringstream ss;
        ss << inet_ntoa(myaddr_.sin_addr) << ":" << ntohs(myaddr_.sin_port);
        return ss.str(); // 返回拼接后的字符串
    }

    // 设置为非阻塞模式
    bool SetNoblock()
    {
        // 1. 获取当前套接字的状态标志
        int flag = fcntl(fd_, F_GETFL);
        // 2. 设置非阻塞标志(O_NONBLOCK),保留原有其他标志
        return fcntl(fd_, F_SETFL, flag | O_NONBLOCK) == 0;
    }

    // 设置为阻塞模式
    bool SetBlock()
    {
        // 1. 获取当前套接字的状态标志(包含所有已设置的标志,如 O_NONBLOCK 等)
        int flags = fcntl(fd_, F_GETFL);
        if (flags == -1) // 获取标志失败(如 fd_ 无效)
        {
            return false;
        }

        // 2. 清除 O_NONBLOCK 标志(保留其他原有标志)
        //    按位与 (~O_NONBLOCK) 表示"除了 O_NONBLOCK 之外的所有位都保留"
        flags &= ~O_NONBLOCK;

        // 3. 设置新的标志(此时已不含 O_NONBLOCK,即回到阻塞模式)
        return fcntl(fd_, F_SETFL, flags) == 0;
    }

    bool IsBlock()
    {
        // 1. 获取 fd 的当前状态标志(F_GETFL 命令)
        int flags = fcntl(fd_, F_GETFL);
        if (flags == -1)
        {
            // 获取标志失败(可能 fd 无效,如已关闭或非法)
            // 可根据需要处理错误,例如打印日志
            // perror("fcntl F_GETFL failed");
            return false; // 错误时返回 false(视为非阻塞或无效状态)
        }

        // 2. 检查是否包含 O_NONBLOCK 标志
        //    按位与结果为 0 → 无 O_NONBLOCK → 阻塞模式
        //    按位与结果非 0 → 有 O_NONBLOCK → 非阻塞模式
        return (flags & O_NONBLOCK) == 0;
    }

private:
    int fd_;
    sockaddr_in myaddr_;
};

// 这是一掉连接,它提供发送、接收、心跳监测,断开的工作
class KcpSession
{
public:
    using Lock = std::unique_lock<std::mutex>;
    using ptr = std::shared_ptr<KcpSession>;

    KcpSession(const KcpOpt &opt, const sockaddr_in &oppaddr, UdpSocket::ptr ptr)
        : opt_(opt), oppaddr_(oppaddr), socket_(ptr)

    {
        kcp_ = ikcp_create(opt_.conv, this);
        kcp_->output = udp_output;
        ikcp_nodelay(kcp_, opt_.nodelay, opt_.interval, opt_.resend, opt_.nc);
        ikcp_wndsize(kcp_, opt_.sndwnd, opt_.rcvwnd);
    }

    ~KcpSession()
    {
        ikcp_release(kcp_);
    }

    void Update(int64_t current)
    {
        {
            // 必须加锁,Update是单独开一个线程执行的,多线程可能出现同时访问snd_buf的场景
            Lock lock(mtx_);
            ikcp_update(kcp_, current);
        }

        if (last_recv_ms == 0)
        {
            last_recv_ms = current;
        }

        // 心跳监测,控制连接状态
        if (opt_.is_server && current - last_recv_ms > opt_.server_keep_alive_timeout)
        {
            TRACE("conv: ", opt_.conv, " timeout"); // 打印超时日志

            state_ = 0;
        }

        if (!opt_.is_server && current - last_send_ms > opt_.client_keep_alive_timeout)
        {
            Send(KEEP_ALIVE_CMD, strlen(KEEP_ALIVE_CMD));
            last_send_ms = current;
            TRACE(KEEP_ALIVE_CMD, strlen(KEEP_ALIVE_CMD));
        }
    }

    int SendTo(const void *buffer, int len)
    {
        int ret = socket_->SendTo(buffer, len, oppaddr_);
        return ret;
    }

    bool Input(const char *buffer, long len)
    {
        Lock lock(mtx_);
        int ret = ikcp_input(kcp_, buffer, len);
        return ret;
    }

    int Recv(char *buffer, int len)
    {
        int ret = 0;
        {
            Lock lock(mtx_);
            ret = ikcp_recv(kcp_, buffer, len);
        }

        if (ret > 0)
        {
            last_recv_ms = current_ms();
        }

        return ret;
    }
    // TRACE("Recv:");

    int Send(const char *buffer, int len)
    {
        Lock lock(mtx_);
        int ret = ikcp_send(kcp_, buffer, len);
        // TRACE("SEND:", buffer);
        return ret;
    }

    void Flush()
    {
        Lock lock(mtx_);
        ikcp_flush(kcp_);
    }

    int Connect()
    {
        Send(CONNECTION_ASK, strlen(CONNECTION_ASK));

        char buffer[BUFFER_LENGTH] = {0};

        int64_t start = current_ms();
        int flag = 0;
        // 记录下当前连接是否为阻塞模式
        if (socket_->IsBlock())
        {
            flag = 1;
        }

        // 这里使用非阻塞模式来遍历,防止阻塞

        socket_->SetNoblock();
        while (true)
        {
            int len = socket_->RecvFrom(buffer, BUFFER_LENGTH, oppaddr_);

            Update(current_ms());
            usleep(10000);

            if (current_ms() - start > CONNECT_TIME_OUT)
            {
                TRACE("CONNECT_TIME_OUT");
                // 恢复阻塞模式
                if (flag == 1)
                {
                    socket_->SetBlock();
                }
                return -1;
            }

            if (len < KCP_HEADER)
            {
                continue;
            }

            else
            {

                Input(buffer, len);
                int recv_len = Recv(buffer, BUFFER_LENGTH);

                if (recv_len <= 0)
                    continue;

                buffer[recv_len] = '\0';
                if (strcmp(buffer, CONNECTION_RES) == 0)
                {
                    TRACE("connect to", GetOppAddrToString());
                    // 恢复阻塞模式
                    if (flag == 1)
                    {
                        socket_->SetBlock();
                    }
                    return 0;
                }
            }
        }
    }

    void SetOppAddr(sockaddr_in oppaddr) { oppaddr_ = oppaddr; }

    int Getconv() { return opt_.conv; }

    bool CheckSpecial(char *buffer)
    {
        if (strcmp(buffer, KEEP_ALIVE_CMD) == 0)
        {
            last_recv_ms = current_ms();
            TRACE("PING");
            return true;
        }

        if (strcmp(buffer, CONNECTION_ASK) == 0)
        {
            TRACE("connected by", GetOppAddrToString());
            Send(CONNECTION_RES, strlen(CONNECTION_RES));
            return true;
        }

        if (strcmp(buffer, CONNECTION_OFF) == 0)
        {
            TRACE("disconnect to", GetOppAddrToString());
            state_ = 0;
            return true;
        }

        if (strcmp(buffer, CONNECTION_RES) == 0)
        {
            TRACE("connect to", GetOppAddrToString());
            return true;
        }

        return false;
    }

    std::string GetOppAddrToString() const
    {
        std::stringstream ss;

        ss << inet_ntoa(oppaddr_.sin_addr) << ":" << ntohs(oppaddr_.sin_port);
        return ss.str();
    }

    UdpSocket::ptr GetSocket() { return socket_; }

    bool state() const { return state_; };

private:
    UdpSocket::ptr socket_;
    ikcpcb *kcp_;
    KcpOpt opt_;
    sockaddr_in oppaddr_;
    int64_t last_send_ms = 0; // 心跳发送的时间 KEEP_ALIVE_CMD (含别的信息接收时间)
    int64_t last_recv_ms = 0; // 心跳发送的时间 KEEP_ALIVE_CMD (不含别的信息发送时间,无法确定update调用是否发送信息出去,所以不计算别的信息发送时间)

    bool state_ = 1;

    std::mutex mtx_;
};

// user必须携带对方的地址,
int udp_output(const char *buf, int len, ikcpcb *kcp, void *user)
{
    KcpSession *session = (KcpSession *)user;
    int ret = session->SendTo(buf, len);
    return ret;
}

#endif

2. kcp_server.cpp

每次收到数据都根据conv去找对应的session 。(类似之前reactor介绍那里根据fd去找对应的conn)这里再次说明了KCP协议给每个对话都赋给conv,实现了逻辑层面上的连接。

cpp 复制代码
#include "kcp_session.hpp"

UdpSocket::ptr server_socket = std::make_shared<UdpSocket>();

std::unordered_map<uint32_t, KcpSession::ptr> session_map;
std::mutex session_map_mutex; // 新增全局锁

KcpSession::ptr NewSession(uint32_t conv, const sockaddr_in &oppo_addr)
{
    TRACE("NewSession");
    KcpOpt opt;
    opt.conv = conv;
    KcpSession::ptr session = std::make_shared<KcpSession>(opt, oppo_addr, server_socket);
    return session;
}

KcpSession::ptr GetSession(uint32_t conv, const sockaddr_in &oppo_addr)
{
    std::lock_guard<std::mutex> lock(session_map_mutex);
    auto it = session_map.find(conv);
    if (it == session_map.end())
    {
        session_map[conv] = NewSession(conv, oppo_addr);
        TRACE("conv: ", conv);
    }

    return session_map[conv];
}

int main()
{
    server_socket->Bind();

    char buffer[BUFFER_LENGTH] = {0};

    std::thread t([&]()
                  {
        while (true)
        {
            usleep(10000); 
            std::lock_guard<std::mutex> lock(session_map_mutex);
            for(auto it = session_map.begin(); it != session_map.end();  )
            {
                it->second->Update(current_ms());
                if (it->second->state() == 0)
                {
                    it->second->Send(CONNECTION_OFF, strlen(CONNECTION_OFF));
                    it->second->Flush();
                    it = session_map.erase(it);
                    
                }
                else
                {
                    it++;
                }
              
            }
        } });

    while (1)
    {
        sockaddr_in client_addr;

        int len = server_socket->RecvFrom(buffer, BUFFER_LENGTH, client_addr);
        if (len < KCP_HEADER)
        {
            continue;
        }
        else
        {
            uint32_t conv = ikcp_getconv(buffer);
            KcpSession::ptr session = GetSession(conv, client_addr);
            session->Input(buffer, len);
            int recv_len;
            recv_len = session->Recv(buffer, BUFFER_LENGTH);
            if (recv_len <= 0) continue;
            buffer[recv_len] = '\0';

            if (!session->CheckSpecial(buffer))
            {
                std::cout << "[Recv from " << session->GetOppAddrToString() << "]: ";
                std::cout << buffer << std::endl;
                session->Send(buffer, recv_len);
            }
        }
    }
}

2. kcp_client.cpp

我们采用UUID算法来生成conv,避免服务器端在客户端连接多的时候出现重复的conv

cpp 复制代码
#include "kcp_session.hpp"
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>

uint32_t GenerateUniqueConv()
{
    // 1. 生成 128 位随机 UUID(版本 4,基于密码学安全随机数)
    boost::uuids::uuid uuid = boost::uuids::random_generator()();

    // 2. 获取 UUID 的原始字节数据(16 字节,uint8_t[16],与 uuid.data() 返回类型一致)
    const uint8_t *uuid_bytes = uuid.data;

    uint32_t conv = 0;
    // 3. 按"4 字节一组"拆分 16 字节 UUID,通过异或累积为 32 位整数
    for (int i = 0; i < 16; i += 4)
    {
        uint32_t chunk = 0;
        // 安全复制 4 字节(UUID 共 16 字节,i 取 0/4/8/12,不会越界)
        std::memcpy(&chunk, &uuid_bytes[i], sizeof(chunk));
        conv ^= chunk; // 异或操作分散随机性,降低碰撞概率
    }

    return conv;
}

int main()
{
    KcpOpt opt;
    opt.is_server = false;
    opt.conv = GenerateUniqueConv();

    UdpSocket::ptr client_socket = std::make_shared<UdpSocket>();

    sockaddr_in server_addr;
    server_addr.sin_addr.s_addr = inet_addr(Server_ip);
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(Server_port);

    KcpSession::ptr session = std::make_shared<KcpSession>(opt, server_addr, client_socket);

    char send_buf[BUFFER_LENGTH + 1] = {0};
    char recv_buf[BUFFER_LENGTH + 1] = {0};

    int ret = session->Connect();
    if (ret == -1)
    {
        return -1;
    }

    std::thread t1([&]()
                   {
        while (true)
        {
            usleep(10000); 
            session->Update(current_ms());
        
            if (session->state() == 0) 
            { 
                std::cout << "server disconnect" << std::endl;
                return 0; 
            }
            
        } });

    std::thread t2([&]()
                   {
        while (true)
        {
           int len = client_socket->RecvFrom(recv_buf, BUFFER_LENGTH, server_addr);

           if (len < KCP_HEADER)
           {
                continue;   
           }

           else
           {
                
                session->Input(recv_buf, len);
                int recv_len = session->Recv(recv_buf, BUFFER_LENGTH);
                
                if(recv_len <= 0) continue;
                
                recv_buf[recv_len] = '\0';
                if (!session->CheckSpecial(recv_buf))
                {
                    std::cout << "[Recv from " << session->GetOppAddrToString() << "]: ";
                    std::cout << recv_buf << std::endl;
                }
            
           }

        } });

    while (1)
    {
        fgets(send_buf, BUFFER_LENGTH, stdin); // 遇到回车结束,将末尾置为'\0'
        send_buf[strcspn(send_buf, "\n")] = '\0';
        int send_len = strlen(send_buf);

        session->Send(send_buf, send_len);
    }
}

4. 测试

测试成功:

客户端发情连接请求,服务端回应,逻辑连接建立成功!!!

客户端定时发送心跳检测,同时客户端主动发送消息,服务端会会主动回显。当客户端异常断开时,服务端检测到,及时清楚逻辑连接。

四、代码总结

1. 断开之后的处理方式由用户自定义

事实上你会发现当连接断开时(state = 0 ),并没有做具体的处理。而是交给用户层去做。

这样使得库的使用性更加广泛,毕竟连接的断开(以上述为例)客户端和服务端的处理逻辑是不同的。

2. KCP协议的使用方式(引入KcpSession后):

2.1 服务端:

建立连接表:unordered_map ( conv --> KcpSession::ptr)

初始化 UdpSocket::ptr

单开线程:遍历map执行, session->Update,同时根据返回值执行具体的断开操作

建立 clientaddr

开始接收数据

获取包的conv --> 根据map去找对应的session、没有则创建

处理数据,利用session精确找到对方,然后回数据

2.2 客户端:

初始化 UdpSocket::ptr

初始化KcpOpt(conv,is_sever)

初始化 KcpSession::ptr

调用Connect:

发送建立连接请求:CONNECTION_ASK

收到回复:CONNECTION_RES

判定逻辑连接建立成功!!!

单开线程:session->Update,同时根据state = 0 时执行具体的断开操作

单开线程:接收数据

单开线程:发送数据

3. 在自定义KcpSession后代码逻辑更加清晰简单了

你会发现,现在用户使用KCP,只需单开一个线程执行Update(相当于内核,在管理连接状态,同时定期发送消息)。然后判断state的值,如果为0就是断开的了,我们执行断开的后续处理。

而用户发数据只需调用Send,收数据只需调用RecvFrom,然后执行Input(相当于将数据包给内核解析),然后再调用Recv就可以收到消息了!!!

相比于之前没有封装,现在使用KCP协议的代码量大大减少了,使用起来更加简单顺手,几乎跟UDP、TCP的使用一个样!!!而且由于封装了锁,收发数据可以正常用于多线程。

相关推荐
西岸行者4 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意4 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码4 天前
嵌入式学习路线
学习
毛小茛4 天前
计算机系统概论——校验码
学习
babe小鑫4 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms4 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下4 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。4 天前
2026.2.25监控学习
学习
im_AMBER4 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J4 天前
从“Hello World“ 开始 C++
c语言·c++·学习