使用 OpenSSL 的 DTLS 中随机缺少连接和消息延迟

问题描述

尝试为 DTLS 编写一个服务器,该服务器当前只会输出它接收到的文本。工作客户端取自 https://github.com/stepheny/openssl-dtls-custom-bio,它可以很好地向自己的服务器发送和接收数据。

然而,当它发送到这个服务器时,一些奇怪的事情发生了。首先连接只是偶尔发生,似乎无法确定连接是否会启动。其次,更奇怪的是数据被“延迟”了。 1 条消息需要发送 6 条消息才能到达。

情况是这样的:

  1. 启动服务器。
  2. 启动客户端。
  3. 希望建立联系。
  4. 如果客户端连接了类型 5 的消息发送到服务器,它们会被发送,但服务器在解码它们时一直出错。
  5. 发送第 6 条消息后,您会注意到第 1 条消息到达服务器。
  6. 发送第 7 个后,您将获得第 2 个。等

需要注意的是,我们不是在谈论时间延迟,没有办法在服务器启动时简单地读取5条空消息,队列为空。只有在发送第 6 条消息后,队列才会填充第 1 条真实消息。

代码

//server.cpp
#include "DTLSConnection.hpp"
#include <iostream>
#include <chrono>
#include <thread>

int main(int argc,char *argv[])
{
    try
    {
        
        DTLSConnection con("192.168.31.177:1235");
        std::cout << "Connection created" << std::endl;
        
        ssize_t ret;
        for(;;)
        {
            ret = con.recv([](Client* c) {
                try{
                    std::cout << c->SSL_read_alt() << std::endl;
                    std::cout << "I am in onmessage" << std::endl;
                }
                catch(std::string &e)
                {
                    std::cerr << "EXCEPTION: " << e << std::endl;
                }
            });
            std::cout << "Returned value is " << ret << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    }
    catch(std::string &e)
    {
        std::cerr << "EXCEPTION: " << e << std::endl;
    }
    return 0;
}

// CustomBIO.hpp
#include <memory>
#include <deque>
#include <vector>
#include <unordered_map>

#include <cstdio> // temporary

#include <cstring>
#include <cassert>
#include <openssl/ssl.h>
#include <signal.h>

const char *sdump_addr(const struct sockaddr *sa)
{
    static char buf[1024];

    switch (sa->sa_family)
    {
        case AF_INET:
            memmove(buf,"INET: ",6);
            inet_ntop(AF_INET,&((struct sockaddr_in *)sa)->sin_addr,buf+6,sizeof(buf)-6);
            sprintf(buf+strlen(buf),":%d",ntohs(((struct sockaddr_in *)sa)->sin_port));
            break;
        case AF_INET6:
            memmove(buf,"INET6: [",8);
            inet_ntop(AF_INET6,&((struct sockaddr_in6 *)sa)->sin6_addr,buf+8,sizeof(buf)-8);
            sprintf(buf+strlen(buf),"]:%d",ntohs(((struct sockaddr_in6 *)sa)->sin6_port));
            break;
        default:
            memmove(buf,"unkNown",8);
            break;
    }

    return buf;
}

struct Packet
{
    size_t capacity = 0;
    size_t len = 0;
    char * data = nullptr;
    
    Packet() = default;
    Packet(size_t cap)
    {
        init(cap);
    }
    Packet(char *b,size_t cap)
    {
        capacity=cap;
        len=capacity;
        data = new char[capacity];
        memcpy(data,b,capacity);
    }
    Packet(char* b,char* e)
    {
        capacity=e-b;
        len=capacity;
        data = new char[capacity];
        memcpy(data,capacity);
    }
    char* begin()
    {
        return data;
    }
    char* end()
    {
        return data ? data+len : nullptr;
    }
    void swap(Packet& that)
    {
        std::swap(this->capacity,that.capacity);
        std::swap(this->len,that.len);
        std::swap(this->data,that.data);
    }
    void init(size_t cap)
    {
        data = new char[cap];
        len = 0;
        capacity = cap;
    }
    void free()
    {
        if(!data) return;
        delete data;
        len = 0;
        capacity = 0;
        data = nullptr;
    }
};

// used for both reading and writing
struct CustomBIO
{
    using dataBuffer = Packet;
    
    int sockfd;
    
    sockaddr_storage thisAddr{};
    socklen_t thisAddr_len{sizeof(sockaddr_storage)};
    
    sockaddr_storage thatAddr{};
    socklen_t thatAddr_len{sizeof(sockaddr_storage)};
    
    template<typename T>
    T* getThat()
    {
        return reinterpret_cast<T*>(&thatAddr);
    }
    
    std::deque<dataBuffer> receivingQueue{};
    bool peekmode{false};
};

inline CustomBIO* BIO_get_CBIO(BIO* b)
{
    return reinterpret_cast<CustomBIO *>(BIO_get_data(b));
}

extern "C"
{
    int BIO_s_custom_write_ex(BIO *b,const char *data,size_t dlen,size_t *written);
    int BIO_s_custom_write(BIO *b,int dlen);
    int BIO_s_custom_read_ex(BIO *b,char *data,size_t *readbytes);
    int BIO_s_custom_read(BIO *b,int dlen);
    int BIO_s_custom_gets(BIO *b,int size);
    int BIO_s_custom_puts(BIO *b,const char *data);
    long BIO_s_custom_ctrl(BIO *b,int cmd,long larg,void *pargs);
    int BIO_s_custom_create(BIO *b);
    int BIO_s_custom_destroy(BIO *b);
    // long BIO_s_custom_callback_ctrl(BIO *,int,BIO_info_cb *);
    
    BIO_METHOD *BIO_s_custom();
    void BIO_s_custom_meth_free();
    
    
    int BIO_s_custom_write_ex(BIO *b,size_t *written)
    {
        fprintf(stderr,"BIO_s_custom_write_ex(BIO[0x%016lX],data[0x%016lX],dlen[%ld],*written[%ld])\n",(long unsigned int)b,(long unsigned int)data,dlen,*written);
        fflush(stderr);
        
        return -1;
    }
    
    int BIO_s_custom_write(BIO *b,int dlen)
    {
        int ret;
        CustomBIO *cbio;
        
        ret = -1;
        fprintf(stderr,"BIO_s_custom_write(BIO[0x%016lX],dlen[%ld])\n",(unsigned long)b,(unsigned long)data,(long)dlen);
        fflush(stderr);
        cbio = BIO_get_CBIO(b);
        
        // dump_addr((struct sockaddr *)&cbio->txaddr,">> ");
        // dump_hex((unsigned const char *)data,"    ");
        ret = sendto(cbio->sockfd,data,cbio->getThat<const sockaddr>(),cbio->thatAddr_len);
        if (ret >= 0)
        {
            fprintf(stderr,"  %d bytes sent\n",ret);
        }
        else
        {
            fprintf(stderr,"  ret: %d errno: [%d] %s\n",ret,errno,strerror(errno));
            fprintf(stderr,"  socket: %d\n",cbio->sockfd);
            fprintf(stderr,"  thatAddrLen: %d\n",cbio->thatAddr_len);
            fprintf(stderr,"  thatAddr: %s\n",sdump_addr(cbio->getThat<sockaddr>()));
        }

        return ret;
    }

    int BIO_s_custom_read_ex(BIO *b,size_t *readbytes)
    {
        fprintf(stderr,"BIO_s_custom_read_ex(BIO[0x%016lX],*readbytes[%ld])\n",(long int)dlen,*readbytes);
        fflush(stderr);
        
        return -1;
    }

    int BIO_s_custom_read(BIO *b,"BIO_s_custom_read(BIO[0x%016lX],(long int)dlen);
        fprintf(stderr,"  probe peekmode %d\n",((CustomBIO *)BIO_get_data(b))->peekmode);
        fflush(stderr);
        
        cbio = BIO_get_CBIO(b);
        if(!cbio->receivingQueue.empty())
        {
            if(cbio->receivingQueue.front().len > (size_t)dlen)
            {
                fprintf(stderr,"if(cbio->receivingQueue.front().len > (size_t)dlen)");
                memmove(data,cbio->receivingQueue.front().data,dlen);
                
                ret = dlen;
                if(!cbio->peekmode)
                {
                    CustomBIO::dataBuffer rest{cbio->receivingQueue.front().begin()+ret,cbio->receivingQueue.front().end()};
                    cbio->receivingQueue.front().swap(rest);
                }
            }
            else
            {
                Packet &pac = cbio->receivingQueue.front();
                ret = pac.len;
                memmove(data,pac.data,ret);
                
                if(!cbio->peekmode)
                {
                    pac.free();
                    cbio->receivingQueue.pop_front();
                }
            }
            fprintf(stderr,"   %d bytes read from queue\n",ret);
            fflush(stderr);
        }
        else
        {
            fprintf(stderr,"   The queue is empty\n");
            /*ret = recvfrom(cbio->sockfd,cbio->getThat<sockaddr>(),&cbio->thatAddr_len); // not right
            if(ret>0 && cbio->peekmode)
            {
                // todo
            }*/
        }
        
        return ret;
    }
    
    int BIO_s_custom_gets(BIO *b,int size)
    {
        fprintf(stderr,"BIO_s_custom_gets(BIO[0x%016lX],size[%d]\n",size);
        if(size <= 1)
        {
            return 0;
        }
        else
        {
            size = BIO_s_custom_read(b,size-1);
            data[size] = '\0';
            return size;
        }
    }
    
    int BIO_s_custom_puts(BIO *b,const char *buf)
    {
        fprintf(stderr,"BIO_s_custom_puts(BIO[0x%016lX],buf[0x%016lX]\n",(long unsigned int)buf);
        size_t size = std::strlen(buf);
        return size > 0 ? BIO_s_custom_write(b,buf,size) : 0;
    }
    
    long BIO_s_custom_ctrl(BIO *b,void *pargs)
    {
        long ret = 0;
        
        fprintf(stderr,"BIO_s_custom_ctrl(BIO[0x%016lX],cmd[%d],larg[%ld],pargs[0x%016lX])\n",cmd,larg,(long unsigned int)pargs);
        if(pargs)
        {
            for(int i=0; ; ++i)
            {
                fprintf(stderr,"[%d]=%X ",i,(int)((unsigned char*)pargs)[i]);
                if(((unsigned char*)pargs)[i] == 0) break;
            }
        }
        fprintf(stderr,"\n");
        fflush(stderr);
        
        switch(cmd)
        {
            case BIO_CTRL_FLUSH: // 11
            case BIO_CTRL_DGRAM_SET_CONNECTED: // 32
            case BIO_CTRL_DGRAM_SET_PEER: // 44
            case BIO_CTRL_DGRAM_GET_PEER: // 46
                ret = 1;
                break;
            case BIO_CTRL_WPENDING: // 13
                ret = 0;
                break;
            case BIO_CTRL_DGRAM_QUERY_MTU: // 40
            case BIO_CTRL_DGRAM_GET_FALLBACK_MTU: // 47
                ret = 1500;
                // ret = 9000; // jumbo?
                break;
            case BIO_CTRL_DGRAM_GET_MTU_OVERHEAD: // 49
                ret = 96; // random guess
                break;
            case BIO_CTRL_DGRAM_SET_PEEK_MODE: // 71
                BIO_get_CBIO(b)->peekmode = (larg != 0);
                ret = 1;
                break;
            case BIO_CTRL_PUSH: // 6
            case BIO_CTRL_POP: // 7
            case BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT: // 45
                ret = 0;
                break;
            default:
                fprintf(stderr,(long unsigned int)pargs);
                fprintf(stderr,"  unkNown cmd: %d\n",cmd);
                fflush(stderr);
                ret = 0;
                break;
        }
        
        return ret;
    }
    
    int BIO_s_custom_create(BIO *b)
    {
        fprintf(stderr,"BIO_s_custom_create(BIO[0x%016lX])\n",(long unsigned int)b);
        fflush(stderr);
        
        return 1;
    }
    
    int BIO_s_custom_destroy(BIO *b)
    {
        fprintf(stderr,"BIO_s_custom_destroy(BIO[0x%016lX])\n",(long unsigned int)b);
        fflush(stderr);
        
        return 1;
    }
    
    BIO_METHOD *_BIO_s_custom = nullptr;
    BIO_METHOD *BIO_s_custom()
    {
        if (!_BIO_s_custom)
        {
            _BIO_s_custom = BIO_meth_new(BIO_get_new_index()|BIO_TYPE_SOURCE_SINK,"BIO_s_custom");
            
            //BIO_meth_set_callback_ctrl(_BIO_s_custom,BIO_s_custom_callback_ctrl);
            BIO_meth_set_create(_BIO_s_custom,BIO_s_custom_create);
            BIO_meth_set_ctrl(_BIO_s_custom,BIO_s_custom_ctrl);
            BIO_meth_set_destroy(_BIO_s_custom,BIO_s_custom_destroy);
            BIO_meth_set_gets(_BIO_s_custom,BIO_s_custom_gets);
            BIO_meth_set_puts(_BIO_s_custom,BIO_s_custom_puts);
            BIO_meth_set_read_ex(_BIO_s_custom,BIO_s_custom_read_ex);
            BIO_meth_set_read(_BIO_s_custom,BIO_s_custom_read);
            BIO_meth_set_write_ex(_BIO_s_custom,BIO_s_custom_write_ex);
            BIO_meth_set_write(_BIO_s_custom,BIO_s_custom_write);
        }
        
        return _BIO_s_custom;
    }
    
    void BIO_s_custom_meth_free()
    {
        if (_BIO_s_custom)
            BIO_meth_free(_BIO_s_custom);

        _BIO_s_custom = NULL;
    }
}

// DTLSConnection.hpp
#include <string>
#include <list>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <sys/epoll.h>

#include <cerrno>

#include <iostream> // temp

#include "CustomBIO.hpp"

constexpr int TIME_OUT = 10000; // ms
char cookie_str[] = "BISCUIT!"; // how to change this
//нужен способ,чтобы клиент был извествен (kNownclients) но еще не подключен (не прошел ssl_accept())
// (void) BIO_dgram_get_peer(SSL_get_rbio(ssl),&peer);
// see https://github.com/nplab/DTLS-Examples/blob/master/src/dtls_udp_echo.c
int generate_cookie([[maybe_unused]] SSL *ssl,unsigned char *cookie,unsigned int *cookie_len)
{
    memmove(cookie,cookie_str,sizeof(cookie_str)-1);
    *cookie_len = sizeof(cookie_str)-1;

    return 1;
}

int verify_cookie([[maybe_unused]] SSL *ssl,const unsigned char *cookie,unsigned int cookie_len)
{
    return sizeof(cookie_str)-1==cookie_len && memcmp(cookie,sizeof(cookie_str)-1)==0;
}

void throw_SSL_error_if_error(SSL* ssl,int ret,std::string str)
{
    if(ret>0) return; // SSL_ERROR_NONE
    
    str += " ret="+std::to_string(ret)+' ';
    auto sslError = SSL_get_error(ssl,ret);
    if(sslError == SSL_ERROR_SYSCALL){
        throw std::string{str+"SSL_ERROR_SYSCALL + error "}+std::to_string(errno);
    }
}

namespace std
{
    template<> struct hash<sockaddr>
    {
        size_t operator()(sockaddr const& val) const noexcept
        {
            size_t res = 0;
            for(unsigned long h : val.sa_data)
            {
                    res = (res << 1) ^ h;
            }
            return res;
        }
    };
}
bool operator==(const sockaddr& l,const sockaddr& r)
{
    if(l.sa_family != r.sa_family) return false;
    for(int i=0; i<14; ++i)
    {
        if(l.sa_data[i] != r.sa_data[i]) return false;
    }
    return true;
}

class DTLSConnection;

class SSLSetterUpper
{
    friend DTLSConnection;
    SSL_CTX *ctx;
    SSLSetterUpper()
    {
        int ret; // because it is C;
        
        SSL_load_error_strings();
        SSL_library_init();
        
        const SSL_METHOD *mtd = DTLS_server_method();
        ctx = SSL_CTX_new(mtd);
        SSL_CTX_set_min_proto_version(ctx,DTLS1_2_VERSION);
        SSL_CTX_use_certificate_chain_file(ctx,"server-cert.pem");
        SSL_CTX_use_PrivateKey_file(ctx,"server-key.pem",SSL_FILETYPE_PEM);
        ret = SSL_CTX_load_verify_locations(ctx,"root-ca.pem",nullptr);
        if(ret != 1)
        {
            throw std::string{"SSL_CTX_load_verify_locations Failed"};
        }
        ret = SSL_CTX_set_default_verify_file(ctx);
        if(ret != 1)
        {
            throw std::string{"SSL_CTX_set_default_verify_file Failed"};
        }
        SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT,nullptr);
        
        SSL_CTX_set_cookie_generate_cb(ctx,generate_cookie);
        SSL_CTX_set_cookie_verify_cb(ctx,verify_cookie);
    }
    
    SSL* generateSSL() const
    {
        return SSL_new(ctx);
    }
};

struct Client
{
    CustomBIO cbio;
    SSL *ssl;
    
    explicit Client(SSL *ssl)
        :cbio{},ssl{ssl}
    {
        cbio.peekmode = false;
        
        BIO *bio = BIO_new(BIO_s_custom());
        BIO_set_data(bio,(void *)&cbio);
        BIO_set_init(bio,1);
        SSL_set_bio(ssl,bio,bio);
    }
    bool on_init() const
    {
        int ret = DTLSv1_listen(this->ssl,nullptr);
        //throw_SSL_error_if_error(ssl,"DTLSv1_listen Failed");
        std::cout << "DTLSv1_listen " << ret << std::endl;
        return (ret==1);
    }
    bool on_connect() const
    {
        int ret = SSL_accept(ssl);
        if(ret!=1) return false;
        std::cout << "ssl = " << SSL_state_string_long(ssl) << std::endl;
        std::cout << "SSL_accept successful!" << std::endl;
        return true;
    }
    std::string SSL_read_alt() const
    {
        Packet p(2000);
        std::cout << "sizeA = " << cbio.receivingQueue.size() << std::endl;
        int ret = ::SSL_read(ssl,p.data,p.capacity);
        std::cout << "ret = " << ret << std::endl;
        std::cout << "sizeB = " << cbio.receivingQueue.size() << std::endl;
        std::cout << "pdata0 " << p.data[0] << std::endl;
        //std::cout << SSL_get_error(ssl,ret) << std::endl;
        throw_SSL_error_if_error(ssl,"sslread");
        p.len = ret;
        std::cerr << "plen" << std::endl;
        std::cerr << p.len << std::endl;
        std::string result(p.data,p.len);
        if(result[0] == '\0') std::cout << "res = " << result << std::endl;
        p.free();
        return result;
    }
};

class DTLSConnection
{
    static const SSLSetterUpper sslSetup;
    
    int epoll_fd;
    
    std::unordered_map<sockaddr,std::shared_ptr<Client>> kNownClients;
    std::unordered_map<sockaddr,std::shared_ptr<Client>> connectedClients;
    std::shared_ptr<Client> incomingClient;
public:
    // 127.0.0.1:1234 or [::1]:1234
    explicit DTLSConnection(std::string thisAddress)
    {
        std::list<sockaddr_storage> addresses;
        
        if (thisAddress[0]=='[')
        {
            auto pos = thisAddress.find(']',1);
            if (pos == std::string::npos)
            {
                throw std::string{"invalid target"};
            }
            int port = std::stoi(thisAddress.substr(pos+2));
            if (port<1||port>65535)
            {
                throw std::string{"invalid port"};
            }
            
            addresses.emplace_back();
            auto* thisAddr = (sockaddr_in6 *)&(addresses.back());
            thisAddr->sin6_family = AF_INET6;

            if ( ! inet_pton(AF_INET6,thisAddress.substr(1,pos).c_str(),&thisAddr->sin6_addr) )
            {
                throw std::string{"invalid ipv6 address"};
            }
            thisAddr->sin6_port = htons(port);
        }
        else
        {
            auto pos = thisAddress.find(':');
            if (pos == std::string::npos)
            {
                throw std::string{"invalid target"};
            }
            int port = std::stoi(thisAddress.substr(pos+1));
            if (port<1||port>65535)
            {
                throw std::string{"invalid port"};
            }

            addresses.emplace_back();
            auto * thisAddr = (sockaddr_in *)&(addresses.back());
            thisAddr->sin_family = AF_INET;

            if ( ! inet_pton(AF_INET,thisAddress.substr(0,&thisAddr->sin_addr) )
            {
                throw std::string{"invalid ipv4 address"};
            }
            thisAddr->sin_port = htons(port);
        }
        
        epoll_fd = epoll_create1(EPOLL_CLOEXEC);
        
        for(auto &address : addresses)
        {
            epoll_event epe {};
            epe.data.fd = socket(address.ss_family,SOCK_DGRAM/*|SOCK_NONBLOCK*/|SOCK_CLOEXEC,0);
            if(bind(epe.data.fd,(const sockaddr*)&address,sizeof(address)) != 0)
            {
                throw std::string{"Failed to bind"};
            }
            epe.events = EPOLLIN|EPOLLET;
            epoll_ctl(epoll_fd,EPOLL_CTL_ADD,epe.data.fd,&epe);
        }
        
        //signal(SIGINT,signal_handler); // do i need this?
        
        incomingClient = std::make_shared<Client>(sslSetup.generateSSL());
    }
    
    ssize_t recv(const std::function<void(Client *)>& onmessage)
    {
        std::cout << "recv(onmessage)" << std::endl;
        epoll_event epe{};
        
        int ret;
        
        ret = epoll_wait(epoll_fd,&epe,1,TIME_OUT);
        if (ret==-1)
        {
            throw std::string{"epoll_wait Failed"};
        }
        if(ret==0)
        {
            return ret; // wait longer
        }
        
        Packet packet{2000};
        
        ret = recvfrom(epe.data.fd,packet.data,packet.capacity,incomingClient->cbio.getThat<sockaddr>(),&incomingClient->cbio.thatAddr_len);
        packet.len = ret;
        
        if(ret==0) return ret;
        if(ret<0)
        {
            switch(errno)
            {
                case EAGAIN:
                //case EWOULDBLOCK:
                    return ret;
                case EBADF:
                    throw std::string{"EBADF"};
                case ECONNREFUSED:
                    throw std::string{"ECONNREFUSED"};
                case EFAULT:
                    throw std::string{"EFAULT"};
                case EINTR:
                    throw std::string{"EINTR"};
                case EINVAL:
                    throw std::string{"EINVAL"};
                case ENOMEM:
                    throw std::string{"ENOMEM"};
                case ENOTCONN:
                    throw std::string{"ENOTCONN"};
                case ENOTSOCK:
                    throw std::string{"ENOTSOCK"};
                default:
                    throw std::string{"Unknwon errno with negative return from recvfrom: "}+std::to_string(errno);
            }
        }
        auto kNown = kNownClients.find(*incomingClient->cbio.getThat<sockaddr>());
        auto connected = connectedClients.find(*incomingClient->cbio.getThat<sockaddr>());
        
        std::cout << "START" << std::endl;
        for(auto &pair : kNownClients)
        {
            std::cout << sdump_addr(&pair.first) << std::endl;
        }
        std::cout << "END" << std::endl;
        if(kNown == kNownClients.end())
        {

            std::cout << "inetaddr = " << sdump_addr(incomingClient->cbio.getThat<sockaddr>()) << std::endl;
            ret = 0;
            incomingClient->cbio.receivingQueue.push_back(std::move(packet));
            
            incomingClient->cbio.sockfd = epe.data.fd;
            if( incomingClient->on_init() )
            {
                std::cout << "inc = " << incomingClient->on_connect() << std::endl; //скорее всего,всегда будет ложь
                std::cout << "on_init if" << std::endl;
                kNownClients[*incomingClient->cbio.getThat<sockaddr>()] = incomingClient;
                
                incomingClient = std::make_shared<Client>(sslSetup.generateSSL());
            }
        }
        else if(connected == connectedClients.end())
        {
            std::cout << "elseif" << std::endl;
            ret = 0;
            auto cli = kNown->second;
            cli->cbio.receivingQueue.push_back(std::move(packet));
            
            if( cli->on_connect() )
            {
                std::cout << "cli->cbio.receivingQueue.size()" << cli->cbio.receivingQueue.size() << std::endl;
                connectedClients[*cli->cbio.getThat<sockaddr>()] = cli;
                SSL_write(cli->ssl,"hello",6);
            }
        }
        else
        {
            std::cout << "else" << std::endl;
            std::cout << sdump_addr(incomingClient->cbio.getThat<sockaddr>()) << " has been found as connected" << std::endl;
            connected->second->cbio.receivingQueue.push_back(std::move(packet));
            onmessage(connected->second.get());
        }

        return ret;
    }
};
const SSLSetterUpper DTLSConnection::sslSetup{};

运行服务器的输出(CustomBIO 输出被截断)是:

Connection created
recv(onmessage)
START
END
inetaddr = INET: 192.168.31.177:58897
  probe peekmode 0
   211 bytes read from queue
  36 bytes sent

  probe peekmode 0
   The queue is empty
DTLSv1_listen -1
Returned value is 0
recv(onmessage)
START
END
inetaddr = INET: 192.168.31.177:58897
  probe peekmode 0
   219 bytes read from queue

DTLSv1_listen 1
  1180 bytes sent
  probe peekmode 0
   The queue is empty
inc = 0
on_init if
Returned value is 0
recv(onmessage)
START
INET: 192.168.31.177:58897
END
elseif
  86 bytes sent
  823 bytes sent
  167 bytes sent
  79 bytes sent
  25 bytes sent
  probe peekmode 0
   219 bytes read from queue
  probe peekmode 0
   The queue is empty
Returned value is 0
recv(onmessage)
START
INET: 192.168.31.177:58897
END
elseif
  probe peekmode 0
   1088 bytes read from queue
  618 bytes sent
ssl = SSL negotiation finished successfully
SSL_accept successful!
cli->cbio.receivingQueue.size()0
  43 bytes sent
Returned value is 0
recv(onmessage)
START
INET: 192.168.31.177:58897
END
else
INET: 192.168.31.177:58897 has been found as connected
sizeA = 1
  probe peekmode 0
   824 bytes read from queue
  probe peekmode 0
   The queue is empty
ret = -1
sizeB = 0
pdata0 �
EXCEPTION: sslread ret=-1 SSL_ERROR_SYSCALL + error 0
Returned value is 824
recv(onmessage)
START
INET: 192.168.31.177:58897
END
else
INET: 192.168.31.177:58897 has been found as connected
sizeA = 1
  probe peekmode 0
   58 bytes read from queue
  probe peekmode 0
   The queue is empty
ret = -1
sizeB = 0
pdata0 `
EXCEPTION: sslread ret=-1 SSL_ERROR_SYSCALL + error 0
Returned value is 58
recv(onmessage)
Returned value is 0
recv(onmessage)
START
INET: 192.168.31.177:58897
END
else
INET: 192.168.31.177:58897 has been found as connected
sizeA = 1
  probe peekmode 0
   131 bytes read from queue
  probe peekmode 0
   The queue is empty
ret = -1
sizeB = 0
pdata0  
EXCEPTION: sslread ret=-1 SSL_ERROR_SYSCALL + error 0
Returned value is 131
recv(onmessage)
START
INET: 192.168.31.177:58897
END
else
INET: 192.168.31.177:58897 has been found as connected
sizeA = 1
  probe peekmode 0
   14 bytes read from queue
  probe peekmode 0
   The queue is empty
ret = -1
sizeB = 0
pdata0 �
EXCEPTION: sslread ret=-1 SSL_ERROR_SYSCALL + error 0
Returned value is 14
recv(onmessage)
START
INET: 192.168.31.177:58897
END
else
INET: 192.168.31.177:58897 has been found as connected
sizeA = 1
   The queue is empty
ret = -1
sizeB = 0
pdata0 
EXCEPTION: sslread ret=-1 SSL_ERROR_SYSCALL + error 0
Returned value is 61
recv(onmessage)
START
INET: 192.168.31.177:58897
END
else
INET: 192.168.31.177:58897 has been found as connected
sizeA = 1
  probe peekmode 0
   55 bytes read from queue
ret = 18
sizeB = 0
pdata0 d
plen
18
dfksaiopfjiaosjfio
I am in onmessage
Returned value is 55
recv(onmessage)
Returned value is 0
recv(onmessage)
START
INET: 192.168.31.177:58897
END
else
INET: 192.168.31.177:58897 has been found as connected
sizeA = 1
  probe peekmode 0
   46 bytes read from queue
ret = 9
sizeB = 0
pdata0 s
plen
9
sdasdasda
I am in onmessage
Returned value is 46
recv(onmessage)
Returned value is 0
recv(onmessage)

您看到 EXCEPTION: sslread ret=-1 SSL_ERROR_SYSCALL + error 0 的地方表示 SSL_read 已返回 -1。抱歉,代码有点脏。

解决方法

以防其他人遇到类似问题。问题是调用服务器的 recv 函数之间的等待时间为 1 秒。那个时候客户端认为服务器没有响应并开始做奇怪的事情。降低延迟解决了问题。