c++ - 在 C/C++ 中同时与 Openssl epoll 服务器通信到多个客户端

标签 c++ c sockets ssl openssl

我有一个 SSL 服务器(下面列出的代码)连接到多个 SSL 客户端。我正在使用单个上下文并且初始化是

SSL_CTX *ctx;
SSL *ssl[MAXEVENTS];
SSL_library_init();
ctx = InitServerCTX();        // initialize SSL
...
...

然后我有下面这段代码

ssl[i] = SSL_new(ctx);           // get new SSL state with context
SSL_set_fd(ssl[i], infd);        // set connection socket to SSL state

然后我执行 SSL_accept(ssl[i])

所有这些都是使用 epoll(边沿触发模式)执行的。我修改了 https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/ 中的示例引用https://www.cs.utah.edu/~swalton/listings/articles/ssl_server.c使用SSL作为引用

逻辑是

 events = new epoll_event[MAXEVENTS * sizeof event];

 // The event loop
 while (true)
 {
    int n, i;

    n = epoll_wait (efd, events, MAXEVENTS, -1);
    for (i = 0; i < n; i++)
    {
        if ((events[i].events & EPOLLERR) ||
                (events[i].events & EPOLLHUP) ||
                (!(events[i].events & EPOLLIN)))
        {
            // An error has occured on this fd, or the socket is not
            // ready for reading (why were we notified then?)
            fprintf (stderr, "epoll error\n");
            close (events[i].data.fd);
            continue;
        } else if (sfd == events[i].data.fd) {
            // We have a notification on the listening socket, which
            // means one or more incoming connections.
            while (1)
            {

                struct sockaddr in_addr;
                socklen_t in_len;
                int infd;
                char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

                in_len = sizeof in_addr;
                infd = accept (sfd, &in_addr, &in_len);
                if (infd == -1)
                {
                    if ((errno == EAGAIN) ||(errno == EWOULDBLOCK)) {
                        // We have processed all incoming
                        // connections.
                        break;
                    } else {
                        perror ("accept");
                        break;
                    }
                }

                s = getnameinfo (&in_addr, in_len,
                                 hbuf, sizeof hbuf,
                                 sbuf, sizeof sbuf,
                                 NI_NUMERICHOST | NI_NUMERICSERV);
                if (s == 0) {
                    printf("Accepted connection on descriptor %d "
                           "(host=%s, port=%s)\n", infd, hbuf, sbuf);
                }


    ssl[i] = SSL_new(ctx);           // get new SSL state with context
    SSL_set_fd(ssl[i], infd);        // set connection socket to SSL state

    int ret;
    if ( (ret=SSL_accept(ssl[i])) == FAIL ) {    // do SSL-protocol accept
    ERR_print_errors_fp(stderr);
    printf("Performing exchange Error 1.\n");
    int error = SSL_get_error(ssl[i], 0);

    //TODO A retry timer or retry counter. Cannot keep retrying perpetually.
    if (ret <=0 && (error == SSL_ERROR_WANT_READ)) {
        //Need to wait until socket is readable. Take action?
        //LOG the reason here
        perror ("Need to wait until socket is readable.");
    } else if (ret <=0 && (error == SSL_ERROR_WANT_WRITE)) {
        //Need to wait until socket is writable. Take action?
        //LOG the reason here
        perror ("Need to wait until socket is writable.");
    } else {
        //LOG the reason here
        perror ("Need to wait until socket is ready.");
    }
    shutdown (infd, 2);
    SSL_free (ssl[i]);
    continue;
    }
    // Make the incoming socket non-blocking and add it to the
    // list of fds to monitor.
    s = SocketNonBlocking (infd);
    if (s == -1) {
    abort ();
    }
    event.data.fd = infd;
    event.events = EPOLLIN | EPOLLET | EPOLLHUP;
    s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);
    if (s == -1) {
    perror ("epoll_ctl");
    abort ();
    }
}
continue;

现在,

    while (1)
    {
        ssize_t count;
        char buf[1024];
        char reply[1024];

        printf("Performing exchange.\n");
        const char* HTMLecho="<html><body><pre>%s</pre></body></html>\n\n";

        ShowCerts(ssl[i]);        // get any certificates
        count = SSL_read(ssl[i], buf, sizeof(buf)); // get request
        int32_t ssl_error = SSL_get_error (ssl[i], count);
        switch (ssl_error) {
         case SSL_ERROR_NONE: 
                    printf("SSL_ERROR_NONE\n");
                    break;
         case SSL_ERROR_WANT_READ:
                    printf("SSL_ERROR_WANT_READ\n");
                    break;
         case SSL_ERROR_WANT_WRITE:
                    printf("SSL_ERROR_WANT_WRITE\n");
                    break;
         case SSL_ERROR_ZERO_RETURN:
                    printf("SSL_ERROR_ZERO_RETURN\n");  
                    break;
         default:
                    break;
         }

        if (( count > 0 ) )
        {
            buf[count] = 0;
            printf("count > 0 Client msg: \"%s\"\n", buf);
            sprintf(reply, HTMLecho, buf);   // construct reply
            SSL_write(ssl[i], reply, strlen(reply)); // send reply
        } else if ((count < 0)  ){
            printf("count < 0 \n");
            if (errno != EAGAIN)
            {
                printf("count < 0 errno != EAGAIN \n");
                perror ("read");
                done = 1;
            }
            break;
        } else if (count==0){
            ERR_print_errors_fp(stderr);
            epoll_ctl(efd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
            printf("count == 0 Client Disconnected.\n");
            done = 1;
            break;
        }

    }
    if (done)
    {
        printf("Freeing data.\n");
        int sd = SSL_get_fd(ssl[i]);
        SSL_free(ssl[i]);         // release SSL state
        close(sd);          // close connection
        //close (events[i].data.fd);
    }
   }

这适用于一台服务器 - 一个客户端。但是当我尝试连接两个客户端时,最后连接的客户端是唯一接收数据的客户端。之前连接的客户端一直挂着,没有任何事件。

更新 我发现这里存在一些索引问题。 epoll 例子中的变量i 的值与我认为应该对应的不对应。我尝试连接两个客户端,最初我认为 i 应该为第二个客户端递增,但事实并非如此。它仍然是 0

最佳答案

好的,我解决了这个问题。我的问题源于不正确的索引。我依赖变量 i那并没有像我预期的那样表现。 (请参阅我的问题中的更新)

首先我声明std::map<int,SSL*> sslPairMap;

然后我将成功的 fd 和 SSL accept 插入到 C++ 中的 std::map 中。在 C 中,可以使用基于结构的配对。这里有一个例子https://github.com/dCache/dcap/blob/b432bd322f0c1cf3e5c6a561845899eec3acad1e/plugins/ssl/sslTunnel.c

                //(c) 2014 enthusiasticgeek for stack overflow

                sslPairMap.insert(std::pair<int,SSL*>(infd, ssl));

                // Make the incoming socket non-blocking and add it to the
                // list of fds to monitor.
                s = AibSocketNonBlocking (infd);
                if (s == -1) {
                    abort ();
                }
                aibevent.data.fd = infd;
                aibevent.events = EPOLLIN | EPOLLET | EPOLLHUP;
                s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &aibevent);
                if (s == -1) {
                    perror ("epoll_ctl");
                    abort ();
                }

在此之后,我只需从 map 中检索 SSL*,以确保我不会无意中更改索引。 std::map 挽救了局面

           //(c) 2014 enthusiasticgeek for stack overflow
           while (1)
            {
                ssize_t count;
                char buf[1024];
                char reply[1024];

                printf("Performing exchange where i = %d.\n",i);
                const char* HTMLecho="<html><body><pre>%s</pre></body></html>\n\n";

                ShowCerts(sslPairMap[aibevents[i].data.fd]);        // get any certificate

                count = SSL_read(sslPairMap[aibevents[i].data.fd], buf, sizeof(buf)); // get request
                ssl_error = SSL_get_error (sslPairMap[aibevents[i].data.fd], count);
                switch (ssl_error) {
                case SSL_ERROR_NONE: 
                            printf("SSL_ERROR_NONE\n");
                            break;
                case SSL_ERROR_WANT_READ:
                            printf("SSL_ERROR_WANT_READ\n");
                            break;
                case SSL_ERROR_WANT_WRITE:
                            printf("SSL_ERROR_WANT_WRITE\n");
                            break;
                case SSL_ERROR_ZERO_RETURN:
                            printf("SSL_ERROR_ZERO_RETURN\n");  
                            break;
                default:
                            break;
                 }

                if (( count > 0 ) )
                {
                    buf[count] = 0;
                    printf("count > 0 Client msg: \"%s\"\n", buf);
                    sprintf(reply, HTMLecho, buf);   // construct reply
                    SSL_write(sslPairMap[aibevents[i].data.fd], reply, strlen(reply)); // send reply
                    break;
                } else if ((count < 0)  ){
                    printf("count < 0 \n");
                    if (errno != EAGAIN)
                    {
                        printf("count < 0 errno != EAGAIN \n");
                        perror ("read");
                        done = 1;
                    }
                    break;
                } else if (count==0){
                    ERR_print_errors_fp(stderr);
                    epoll_ctl(efd, EPOLL_CTL_DEL, aibevents[i].data.fd, NULL);
                    printf("count == 0 Client Disconnected.\n");
                    done = 1;
                    break;
                }

            }
            if (done)
            {
                printf("Freeing data.\n");
                int sd = SSL_get_fd(sslPairMap[aibevents[i].data.fd]);
                if(ssl_error == SSL_ERROR_NONE){
                   SSL_shutdown(sslPairMap[aibevents[i].data.fd]);
                }
                SSL_free(sslPairMap[aibevents[i].data.fd]);         // release SSL state
                close(sd);          // close connection
                //close (aibevents[i].data.fd);
                erase_from_map(sslPairMap, aibevents[i].data.fd);
            }
        }

关于c++ - 在 C/C++ 中同时与 Openssl epoll 服务器通信到多个客户端,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23622053/

相关文章:

c - 读取 csv 文件并存储在缓冲区中时出现问题

javascript - 显示每个套接字连接的 div

c++ - 内存和范围管理

c++对象数组,初始化失败

c++ - 怎么可能在 C++ 中的 main() 中不声明任何内容,但编译后却有一个工作应用程序?

c - 如何让 Dev-C++ 从 C 代码生成英特尔程序集?

c++ - 包含 <algorithm> 和 <limits> 会导致 "invalid pure specifier"编译错误

c - 有没有一个函数可以将char数组输入的数字转换为int数组?

linux - Apache http 服务器无法重新启动

sockets - 每台服务器的套接字连接的实际/硬性限制是多少