簡體   English   中英

在 C/C++ 中與 Openssl epoll 服務器同時與多個客戶端通信

[英]Communication with Openssl epoll server to multiple clients simultaneously in C/C++

我有一個 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使用 SSL。 utah.edu/~swalton/listings/articles/ssl_server.c作為參考

這樣做的邏輯是

 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 接受插入到 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 ();
                }

在此之后,我只需從地圖中檢索 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);
            }
        }

如果有人遇到這種情況,另一種方法是將SSL*指針存儲在事件數據本身中:

events[i].data.u64 = (long long)ssl;

當您需要從中讀取/寫入時:

auto ssl = (SSL*)events[i].data.u64;
SSL_read(ssl, someBuffer, sizeof(someBuffer));

例如。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM