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的使用一个样!!!而且由于封装了锁,收发数据可以正常用于多线程。

相关推荐
立志成为大牛的小牛6 小时前
数据结构——二十三、并查集的终极优化(王道408)
开发语言·数据结构·笔记·学习·程序人生·考研
QT 小鲜肉7 小时前
【个人成长笔记】Qt Creator快捷键终极指南:从入门到精通
开发语言·c++·笔记·qt·学习·学习方法
QT 小鲜肉10 小时前
【数据结构与算法基础】05. 栈详解(C++ 实战)
开发语言·数据结构·c++·笔记·学习·算法·学习方法
A9better10 小时前
嵌入式开发学习日志40——stm32之I2C协议层
stm32·单片机·嵌入式硬件·学习
ha204289419411 小时前
Linux操作系统学习之---线程控制
java·linux·学习
Laplaces Demon13 小时前
Spring 源码学习(十四)—— HandlerMethodArgumentResolver
java·开发语言·学习
青衫码上行13 小时前
【从0开始学习Java | 第22篇】反射
java·开发语言·学习
hmbbcsm13 小时前
python学习之路(四)
学习
Greedy Alg14 小时前
Socket编程学习记录
网络·websocket·学习