Inomboniso ogcweleyo ongenanto-C I/O reactor

Inomboniso ogcweleyo ongenanto-C I/O reactor

Intshayelelo

I/O irector (umsonto omnye iluphu yesiganeko) yipatheni yokubhala isoftware enomthwalo omkhulu, esetyenziswa kwizisombululo ezininzi ezidumileyo:

Kule nqaku, siza kujonga i-ins kunye nokuphuma kwe-reactor ye-I / O kunye nendlela esebenza ngayo, bhala ukuphunyezwa kwimigca engaphantsi kwe-200 yekhowudi, kwaye wenze inkqubo yomncedisi we-HTTP elula ngaphezu kwezicelo zezigidi ze-40 / min.

I ngcaciso

  • Inqaku libhalelwe ukunceda ukuqonda ukusebenza kwe-reactor ye-I / O, kwaye ngoko ke uqonde ingozi xa usebenzisa.
  • Ulwazi lweziseko luyafuneka ukuze uliqonde inqaku. C ulwimi kunye namava athile kuphuhliso lwesicelo sothungelwano.
  • Yonke ikhowudi ibhalwe ngolwimi C ngokungqongqo ngokwe (isilumkiso: ende PDF) ukuya kwi-C11 esemgangathweni yeLinux kwaye iyafumaneka kwi GitHub.

Kutheni oku kuyimfuneko?

Ngokukhula kokuthandwa kwe-Intanethi, abancedisi bewebhu baqala ukufuna ukuphatha inani elikhulu loqhagamshelo ngaxeshanye, kwaye ke ngoko kwazanywa iindlela ezimbini: ukuthintela i-I / O kwinani elikhulu lemicu ye-OS kunye ne-O / O engavaliyo ngokudibanisa inkqubo yokwazisa ngesiganeko, ekwabizwa ngokuba “ngumkhethi wenkqubo” (epoll/equeue/IOCP/ njl).

Indlela yokuqala ibandakanya ukudala intambo entsha ye-OS kuqhagamshelwano ngalunye olungenayo. Ukungonakali kwayo kukungalingani kakuhle: inkqubo yokusebenza kuya kufuneka iphumeze uninzi iinguqu zemeko и inkqubo iminxeba. Ziyimisebenzi ebiza kakhulu kwaye inokukhokelela ekunqongopheni kwe-RAM yasimahla enenani elinomtsalane lonxibelelwano.

Uguqulelo olulungisiweyo lubalaselisa inani elimiselweyo lemisonto (umcu wequla), ngaloo ndlela uthintela inkqubo ekuphuhliseni ukubulawa, kodwa kwangaxeshanye ingenisa ingxaki entsha: ukuba iqula livalwe ngoku yimisebenzi yokufunda ixesha elide, ke ezinye iisokethi esele zikwazi ukufumana idatha aziyi kukwazi yenza njalo.

Indlela yesibini isebenzisa inkqubo yesaziso somsitho (umkhethi wenkqubo) enikezwe yi-OS. Eli nqaku lixubusha uhlobo oluqhelekileyo lomkhethi wenkqubo, ngokusekelwe kwizilumkiso (iziganeko, izaziso) malunga nokulungela imisebenzi ye-I / O, kunokuba izaziso malunga nokugqitywa kwazo. Umzekelo owenziwe lula wokusetyenziswa kwawo unokumelwa ngulo mzobo webhloko ulandelayo:

Inomboniso ogcweleyo ongenanto-C I/O reactor

Umahluko phakathi kwezi ndlela zilandelayo:

  • Ukuthintela imisebenzi ye-I/O nqumamisa ukuhamba komsebenzisi dede i-OS ilungile defragments engenayo iipakethe IP ukuya kumlambo (TCP, ukufumana idatha) okanye akuyi kubakho sithuba saneleyo esifumanekayo kwizithinteli zokubhala zangaphakathi zokuthumela ngokulandelayo nge NIC (ukuthumela idatha).
  • Umkhethi wenkqubo ixesha elidlulele yazisa inkqubo ukuba i-OS sele sele iipakethi ze-IP ezihluthiweyo (i-TCP, i-data reception) okanye indawo eyaneleyo kwi-buffers yokubhala yangaphakathi sele sele ekhoyo (ukuthumela idatha).

Ukuyishwankathela, ukugcina intambo ye-OS kwi-I/O nganye yinkcitho yamandla ekhompyuter, kuba eneneni, imisonto ayenzi umsebenzi oluncedo (kungoko igama elithi "ukuphazamiseka kwesoftware"). Umkhethi wenkqubo usombulula le ngxaki, evumela inkqubo yomsebenzisi ukuba isebenzise izixhobo ze-CPU ngakumbi ngokwezoqoqosho.

I/O imodeli yereactor

I-reactor ye-I/O isebenza njengomaleko phakathi komkhethi wenkqubo kunye nekhowudi yomsebenzisi. Umgaqo wokusebenza kwawo uchazwa ngulo mzobo webhloko ulandelayo:

Inomboniso ogcweleyo ongenanto-C I/O reactor

  • Makhe ndikukhumbuze ukuba isiganeko sisaziso sokuba i-socket ethile iyakwazi ukwenza umsebenzi ongathinteliyo we-I / O.
  • Umphathi wesiganeko ngumsebenzi obizwa ngokuba yi-I/O reactor xa isiganeko samkelwe, esithi ke senze umsebenzi we-I/O ongathinteliyo.

Kubalulekile ukuqaphela ukuba i-reactor ye-I / O yinkcazo yomsonto omnye, kodwa akukho nto ithintela ingcamango ukuba isetyenziswe kwindawo enemisonto emininzi kumlinganiselo we-1 intambo: i-reactor ye-1, ngaloo ndlela iphinda isetyenziswe zonke ii-CPU cores.

Ukuphunyezwa

Siza kubeka ujongano loluntu kwifayile reactor.h, kunye nokuphunyezwa - ngaphakathi reactor.c. reactor.h iya kuba nezi saziso zilandelayo:

Bonisa izibhengezo kwireactor.h

typedef struct reactor Reactor;

/*
 * Указатель на функцию, которая будет вызываться I/O реактором при поступлении
 * события от системного селектора.
 */
typedef void (*Callback)(void *arg, int fd, uint32_t events);

/*
 * Возвращает `NULL` в случае ошибки, не-`NULL` указатель на `Reactor` в
 * противном случае.
 */
Reactor *reactor_new(void);

/*
 * Освобождает системный селектор, все зарегистрированные сокеты в данный момент
 * времени и сам I/O реактор.
 *
 * Следующие функции возвращают -1 в случае ошибки, 0 в случае успеха.
 */
int reactor_destroy(Reactor *reactor);

int reactor_register(const Reactor *reactor, int fd, uint32_t interest,
                     Callback callback, void *callback_arg);
int reactor_deregister(const Reactor *reactor, int fd);
int reactor_reregister(const Reactor *reactor, int fd, uint32_t interest,
                       Callback callback, void *callback_arg);

/*
 * Запускает цикл событий с тайм-аутом `timeout`.
 *
 * Эта функция передаст управление вызывающему коду если отведённое время вышло
 * или/и при отсутствии зарегистрированных сокетов.
 */
int reactor_run(const Reactor *reactor, time_t timeout);

Ulwakhiwo lwe-I/O lwe-reactor lubandakanya isichazi sefayile umkhethi epoll и iitafile zehash GHashTable, ebonisa isiseko ngasinye CallbackData (isakhiwo somphathi wesiganeko kunye nengxoxo yomsebenzisi ngayo).

Bonisa iReactor kunye neCallbackData

struct reactor {
    int epoll_fd;
    GHashTable *table; // (int, CallbackData)
};

typedef struct {
    Callback callback;
    void *arg;
} CallbackData;

Nceda uqaphele ukuba senze isakhono sokusingatha uhlobo olungaphelelanga ngokwesalathiso. IN reactor.h sibhengeza isakhiwo reactor, kunye reactor.c siyayichaza, ngaloo ndlela sithintela umsebenzisi ekutshintsheni iindawo zayo. Le yenye yeepateni ifihla idatha, ethi ingene ngokuthe ngqo kwi-C semantics.

Imisebenzi reactor_register, reactor_deregister и reactor_reregister hlaziya uluhlu lweziseko zomdla kunye nabaphethe isiganeko esihambelanayo kumkhethi wenkqubo kunye netafile yehash.

Bonisa imisebenzi yobhaliso

#define REACTOR_CTL(reactor, op, fd, interest)                                 
    if (epoll_ctl(reactor->epoll_fd, op, fd,                                   
                  &(struct epoll_event){.events = interest,                    
                                        .data = {.fd = fd}}) == -1) {          
        perror("epoll_ctl");                                                   
        return -1;                                                             
    }

int reactor_register(const Reactor *reactor, int fd, uint32_t interest,
                     Callback callback, void *callback_arg) {
    REACTOR_CTL(reactor, EPOLL_CTL_ADD, fd, interest)
    g_hash_table_insert(reactor->table, int_in_heap(fd),
                        callback_data_new(callback, callback_arg));
    return 0;
}

int reactor_deregister(const Reactor *reactor, int fd) {
    REACTOR_CTL(reactor, EPOLL_CTL_DEL, fd, 0)
    g_hash_table_remove(reactor->table, &fd);
    return 0;
}

int reactor_reregister(const Reactor *reactor, int fd, uint32_t interest,
                       Callback callback, void *callback_arg) {
    REACTOR_CTL(reactor, EPOLL_CTL_MOD, fd, interest)
    g_hash_table_insert(reactor->table, int_in_heap(fd),
                        callback_data_new(callback, callback_arg));
    return 0;
}

Emva kokuba i-reactor ye-I / O ithintele isiganeko kunye nesichazi fd, ibiza umphathi wesiganeko esihambelanayo, apho idlula khona fd, imaski encinci iziganeko ezenziweyo kunye nesalathisi somsebenzisi void.

Bonisa ireactor_run () umsebenzi

int reactor_run(const Reactor *reactor, time_t timeout) {
    int result;
    struct epoll_event *events;
    if ((events = calloc(MAX_EVENTS, sizeof(*events))) == NULL)
        abort();

    time_t start = time(NULL);

    while (true) {
        time_t passed = time(NULL) - start;
        int nfds =
            epoll_wait(reactor->epoll_fd, events, MAX_EVENTS, timeout - passed);

        switch (nfds) {
        // Ошибка
        case -1:
            perror("epoll_wait");
            result = -1;
            goto cleanup;
        // Время вышло
        case 0:
            result = 0;
            goto cleanup;
        // Успешная операция
        default:
            // Вызвать обработчиков событий
            for (int i = 0; i < nfds; i++) {
                int fd = events[i].data.fd;

                CallbackData *callback =
                    g_hash_table_lookup(reactor->table, &fd);
                callback->callback(callback->arg, fd, events[i].events);
            }
        }
    }

cleanup:
    free(events);
    return result;
}

Ukushwankathela, uluhlu lweefowuni zokusebenza kwikhowudi yomsebenzisi ziya kuthatha le fomu ilandelayo:

Inomboniso ogcweleyo ongenanto-C I/O reactor

Iseva enomsonto omnye

Ukuze uvavanye i-reactor ye-I / O phantsi komthwalo ophezulu, siya kubhala iseva yewebhu ye-HTTP elula ephendula nasiphi na isicelo esinomfanekiso.

Ireferensi ekhawulezayo kwiprotocol yeHTTP

HTTP - le yiprotocol inqanaba lesicelo, isetyenziswa ikakhulu kunxibelelwano lwe-server-browser.

I-HTTP inokusetyenziswa ngokulula ngaphezulu uthutho umthetho olandelwayo TCP, ukuthumela nokufumana imiyalezo ngefomathi echaziweyo iinkcukacha.

iFomathi yesicelo

<КОМАНДА> <URI> <ВЕРСИЯ HTTP>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ>

  • CRLF lulandelelwano lwabalinganiswa ababini: r и n, ukwahlula umgca wokuqala wesicelo, iiheader kunye nedatha.
  • <КОМАНДА> - enye ye CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Isikhangeli siya kuthumela umyalelo kwiseva yethu GET, okuthetha ukuthi "Ndithumele imixholo yefayile."
  • <URI> - isazisi esifanayo somthombo. Umzekelo, ukuba URI = /index.html, ngoko umxhasi ucela elona phepha lendawo.
  • <ВЕРСИЯ HTTP> — uguqulelo lweprotocol yeHTTP kwifomathi HTTP/X.Y. Eyona nguqulo isetyenziswa kakhulu namhlanje HTTP/1.1.
  • <ЗАГОЛОВОК N> sisitshixo-xabiso iperi kwifomati <КЛЮЧ>: <ЗНАЧЕНИЕ>, ithunyelwe kumncedisi ukuze kuhlalutywe ngakumbi.
  • <ДАННЫЕ> — idatha efunwa ngumncedisi ukwenza umsebenzi. Amaxesha amaninzi ilula JSON okanye nayiphi na enye indlela.

Indlela Yokuphendula

<ВЕРСИЯ HTTP> <КОД СТАТУСА> <ОПИСАНИЕ СТАТУСА>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ>

  • <КОД СТАТУСА> linani elimele isiphumo somsebenzi. Umncedisi wethu uya kuhlala ebuyisela isimo se-200 (ukusebenza ngempumelelo).
  • <ОПИСАНИЕ СТАТУСА> — ukumelwa komtya wekhowudi yesimo. Kwikhowudi yesimo 200 oku OK.
  • <ЗАГОЛОВОК N> — iheader yohlobo olufanayo nolukwisicelo. Siza kubuyisela izihloko Content-Length (ubungakanani befayile) kunye Content-Type: text/html (ubuyise uhlobo lwedatha).
  • <ДАННЫЕ> - idatha ecelwe ngumsebenzisi. Kwimeko yethu, le yindlela eya kumfanekiso HTML.

Ifayile http_server.c (umncedisi womsonto omnye) uquka ifayile common.h, equlathe ezi prototypes zomsebenzi zilandelayo:

Bonisa iiprototypes zomsebenzi ngokufanayo.h

/*
 * Обработчик событий, который вызовется после того, как сокет будет
 * готов принять новое соединение.
 */
static void on_accept(void *arg, int fd, uint32_t events);

/*
 * Обработчик событий, который вызовется после того, как сокет будет
 * готов отправить HTTP ответ.
 */
static void on_send(void *arg, int fd, uint32_t events);

/*
 * Обработчик событий, который вызовется после того, как сокет будет
 * готов принять часть HTTP запроса.
 */
static void on_recv(void *arg, int fd, uint32_t events);

/*
 * Переводит входящее соединение в неблокирующий режим.
 */
static void set_nonblocking(int fd);

/*
 * Печатает переданные аргументы в stderr и выходит из процесса с
 * кодом `EXIT_FAILURE`.
 */
static noreturn void fail(const char *format, ...);

/*
 * Возвращает файловый дескриптор сокета, способного принимать новые
 * TCP соединения.
 */
static int new_server(bool reuse_port);

I-macro esebenzayo nayo ichazwe SAFE_CALL() kwaye umsebenzi uchaziwe fail(). I-macro ithelekisa ixabiso lentetho kunye nempazamo, kwaye ukuba imeko iyinyani, ibiza umsebenzi fail():

#define SAFE_CALL(call, error)                                                 
    do {                                                                       
        if ((call) == error) {                                                   
            fail("%s", #call);                                                 
        }                                                                      
    } while (false)

Umsebenzi fail() iprinta iingxoxo ezigqithisiweyo kwi-terminal (njenge printf()) kwaye iphelisa inkqubo ngekhowudi EXIT_FAILURE:

static noreturn void fail(const char *format, ...) {
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    fprintf(stderr, ": %sn", strerror(errno));
    exit(EXIT_FAILURE);
}

Umsebenzi new_server() ibuyisela inkcazo yefayile ye "server" socket eyenziwe yiminxeba yesixokelelwano socket(), bind() и listen() kwaye ekwaziyo ukwamkela uqhagamshelwano olungenayo ngendlela engathinteliyo.

Bonisa umncedisi omtsha () umsebenzi

static int new_server(bool reuse_port) {
    int fd;
    SAFE_CALL((fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)),
              -1);

    if (reuse_port) {
        SAFE_CALL(
            setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int)),
            -1);
    }

    struct sockaddr_in addr = {.sin_family = AF_INET,
                               .sin_port = htons(SERVER_PORT),
                               .sin_addr = {.s_addr = inet_addr(SERVER_IPV4)},
                               .sin_zero = {0}};

    SAFE_CALL(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), -1);
    SAFE_CALL(listen(fd, SERVER_BACKLOG), -1);
    return fd;
}

  • Qaphela ukuba i-socket yenziwe ekuqaleni kwimodi yokungathinteli usebenzisa iflegi SOCK_NONBLOCKukuze kumsebenzi on_accept() (funda ngakumbi) umnxeba wenkqubo accept() khange kumise ukwenziwa komsonto.
  • ukuba reuse_port iyalingana true, ngoko lo msebenzi uzakuqwalasela isiseko ngokhetho SO_REUSEPORT nge setsockopt()ukusebenzisa izibuko elifanayo kwindawo enemisonto emininzi (bona icandelo “Umncedisi onemisonto emininzi”).

Umphathi wesiganeko on_accept() ebizwa emva kokuba OS ivelisa isiganeko EPOLLIN, kule meko ithetha ukuba uqhagamshelwano olutsha lunokwamkelwa. on_accept() yamkela uxhulumaniso olutsha, lutshintshela kwindlela yokungathinteli kwaye irejista kunye nomphathi wesiganeko on_recv() kwi-I/O reactor.

Bonisa ku_ukwamkela () umsebenzi

static void on_accept(void *arg, int fd, uint32_t events) {
    int incoming_conn;
    SAFE_CALL((incoming_conn = accept(fd, NULL, NULL)), -1);
    set_nonblocking(incoming_conn);
    SAFE_CALL(reactor_register(reactor, incoming_conn, EPOLLIN, on_recv,
                               request_buffer_new()),
              -1);
}

Umphathi wesiganeko on_recv() ebizwa emva kokuba OS ivelisa isiganeko EPOLLIN, kule meko ithetha ukuba unxibelelwano lubhalisiwe on_accept(), ukulungele ukufumana idatha.

on_recv() ifunda idatha ukusuka kuqhagamshelo de isicelo seHTTP sifunyenwe ngokupheleleyo, emva koko ibhalisa umphathi on_send() ukuthumela impendulo yeHTTP. Ukuba umxhasi uyaphula uxhulumaniso, i-socket iyacinywa kwaye ivalwe ngokusebenzisa close().

Bonisa umsebenzi kwi_recv()

static void on_recv(void *arg, int fd, uint32_t events) {
    RequestBuffer *buffer = arg;

    // Принимаем входные данные до тех пор, что recv возвратит 0 или ошибку
    ssize_t nread;
    while ((nread = recv(fd, buffer->data + buffer->size,
                         REQUEST_BUFFER_CAPACITY - buffer->size, 0)) > 0)
        buffer->size += nread;

    // Клиент оборвал соединение
    if (nread == 0) {
        SAFE_CALL(reactor_deregister(reactor, fd), -1);
        SAFE_CALL(close(fd), -1);
        request_buffer_destroy(buffer);
        return;
    }

    // read вернул ошибку, отличную от ошибки, при которой вызов заблокирует
    // поток
    if (errno != EAGAIN && errno != EWOULDBLOCK) {
        request_buffer_destroy(buffer);
        fail("read");
    }

    // Получен полный HTTP запрос от клиента. Теперь регистрируем обработчика
    // событий для отправки данных
    if (request_buffer_is_complete(buffer)) {
        request_buffer_clear(buffer);
        SAFE_CALL(reactor_reregister(reactor, fd, EPOLLOUT, on_send, buffer),
                  -1);
    }
}

Umphathi wesiganeko on_send() ebizwa emva kokuba OS ivelisa isiganeko EPOLLOUT, oku kuthetha ukuba unxibelelwano lubhalisiwe on_recv(), ilungele ukuthumela idatha. Lo msebenzi uthumela impendulo ye-HTTP equlethe i-HTML ngomfanekiso kumxhasi kwaye emva koko utshintshe umphathi wesiganeko emva on_recv().

Bonisa kwi_send () umsebenzi

static void on_send(void *arg, int fd, uint32_t events) {
    const char *content = "<img "
                          "src="https://habrastorage.org/webt/oh/wl/23/"
                          "ohwl23va3b-dioerobq_mbx4xaw.jpeg">";
    char response[1024];
    sprintf(response,
            "HTTP/1.1 200 OK" CRLF "Content-Length: %zd" CRLF "Content-Type: "
            "text/html" DOUBLE_CRLF "%s",
            strlen(content), content);

    SAFE_CALL(send(fd, response, strlen(response), 0), -1);
    SAFE_CALL(reactor_reregister(reactor, fd, EPOLLIN, on_recv, arg), -1);
}

Kwaye ekugqibeleni, kwifayile http_server.c, kumsebenzi main() sidala i-I/O reactor sisebenzisa reactor_new(), yenza isiseko somncedisi kwaye ubhalise, qalisa i-reactor usebenzisa reactor_run() ngokuthe ngqo umzuzu omnye, kwaye emva koko sikhulula izixhobo kwaye siphume kwinkqubo.

Bonisa i-http_server.c

#include "reactor.h"

static Reactor *reactor;

#include "common.h"

int main(void) {
    SAFE_CALL((reactor = reactor_new()), NULL);
    SAFE_CALL(
        reactor_register(reactor, new_server(false), EPOLLIN, on_accept, NULL),
        -1);
    SAFE_CALL(reactor_run(reactor, SERVER_TIMEOUT_MILLIS), -1);
    SAFE_CALL(reactor_destroy(reactor), -1);
}

Masijonge ukuba yonke into isebenza njengoko bekulindelekile. Ukuqulunqa (chmod a+x compile.sh && ./compile.sh kwingcambu yeprojekthi) kwaye uqalise umncedisi ozibhalayo, vula http://127.0.0.1:18470 kwisikhangeli kwaye sibone ukuba besilindele ntoni:

Inomboniso ogcweleyo ongenanto-C I/O reactor

Umlinganiselo wokusebenza

Bonisa iinkcukacha zemoto yam

$ screenfetch
 MMMMMMMMMMMMMMMMMMMMMMMMMmds+.        OS: Mint 19.1 tessa
 MMm----::-://////////////oymNMd+`     Kernel: x86_64 Linux 4.15.0-20-generic
 MMd      /++                -sNMd:    Uptime: 2h 34m
 MMNso/`  dMM    `.::-. .-::.` .hMN:   Packages: 2217
 ddddMMh  dMM   :hNMNMNhNMNMNh: `NMm   Shell: bash 4.4.20
     NMm  dMM  .NMN/-+MMM+-/NMN` dMM   Resolution: 1920x1080
     NMm  dMM  -MMm  `MMM   dMM. dMM   DE: Cinnamon 4.0.10
     NMm  dMM  -MMm  `MMM   dMM. dMM   WM: Muffin
     NMm  dMM  .mmd  `mmm   yMM. dMM   WM Theme: Mint-Y-Dark (Mint-Y)
     NMm  dMM`  ..`   ...   ydm. dMM   GTK Theme: Mint-Y [GTK2/3]
     hMM- +MMd/-------...-:sdds  dMM   Icon Theme: Mint-Y
     -NMm- :hNMNNNmdddddddddy/`  dMM   Font: Noto Sans 9
      -dMNs-``-::::-------.``    dMM   CPU: Intel Core i7-6700 @ 8x 4GHz [52.0°C]
       `/dMNmy+/:-------------:/yMMM   GPU: NV136
          ./ydNMMMMMMMMMMMMMMMMMMMMM   RAM: 2544MiB / 7926MiB
             .MMMMMMMMMMMMMMMMMMM

Makhe silinganise ukusebenza kweseva enomsonto omnye. Masivule iitheminali ezimbini: kwenye siya kubaleka ./http_server, ngenye indlela - Imeko. Emva komzuzu, ezi nkcukacha-manani zilandelayo ziya kuboniswa kwi-terminal yesibini:

$ wrk -c100 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: en-US,en;q=0.5" -H "Connection: keep-alive"
Running 1m test @ http://127.0.0.1:18470
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   493.52us   76.70us  17.31ms   89.57%
    Req/Sec    24.37k     1.81k   29.34k    68.13%
  11657769 requests in 1.00m, 1.60GB read
Requests/sec: 193974.70
Transfer/sec:     27.19MB

Iseva yethu enomsonto omnye ikwazile ukwenza izicelo ezingaphezulu kwezigidi ezili-11 ngomzuzu ezisuka kwi-100 yoqhakamshelwano. Ayinasiphumo esibi, kodwa ngaba inokuphuculwa?

Iseva enemisonto emininzi

Njengoko kukhankanyiwe ngasentla, i-reactor ye-I/O inokudalwa kwimisonto eyahlukileyo, ngaloo ndlela isebenzisa zonke ii-CPU cores. Masisebenzise le ndlela yokusebenza:

Bonisa i-http_server_multithreaded.c

#include "reactor.h"

static Reactor *reactor;
#pragma omp threadprivate(reactor)

#include "common.h"

int main(void) {
#pragma omp parallel
    {
        SAFE_CALL((reactor = reactor_new()), NULL);
        SAFE_CALL(reactor_register(reactor, new_server(true), EPOLLIN,
                                   on_accept, NULL),
                  -1);
        SAFE_CALL(reactor_run(reactor, SERVER_TIMEOUT_MILLIS), -1);
        SAFE_CALL(reactor_destroy(reactor), -1);
    }
}

Ngoku yonke intambo owakhe i-reactor:

static Reactor *reactor;
#pragma omp threadprivate(reactor)

Nceda uqaphele ukuba impikiswano yomsebenzi new_server() abacebisi true. Oku kuthetha ukuba sinikezela ukhetho kwisokethi yomncedisi SO_REUSEPORTukuyisebenzisa kwindawo enemisonto emininzi. Unokufunda ngakumbi apha.

Ukubaleka okwesibini

Ngoku makhe silinganise ukusebenza kweseva enemisonto emininzi:

$ wrk -c100 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: en-US,en;q=0.5" -H "Connection: keep-alive"
Running 1m test @ http://127.0.0.1:18470
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.14ms    2.53ms  40.73ms   89.98%
    Req/Sec    79.98k    18.07k  154.64k    78.65%
  38208400 requests in 1.00m, 5.23GB read
Requests/sec: 635876.41
Transfer/sec:     89.14MB

Inani lezicelo eziqhutywe ngomzuzu o-1 linyuke nge ~ 3.28 amaxesha! Kodwa besizizigidi ezi-XNUMX kuphela kwinani elijikelezayo, ngoko ke makhe sizame ukuyilungisa loo nto.

Okokuqala makhe sijonge izibalo ezenziweyo Isitshisi:

$ sudo perf stat -B -e task-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,branches,branch-misses,cache-misses ./http_server_multithreaded

 Performance counter stats for './http_server_multithreaded':

     242446,314933      task-clock (msec)         #    4,000 CPUs utilized          
         1 813 074      context-switches          #    0,007 M/sec                  
             4 689      cpu-migrations            #    0,019 K/sec                  
               254      page-faults               #    0,001 K/sec                  
   895 324 830 170      cycles                    #    3,693 GHz                    
   621 378 066 808      instructions              #    0,69  insn per cycle         
   119 926 709 370      branches                  #  494,653 M/sec                  
     3 227 095 669      branch-misses             #    2,69% of all branches        
           808 664      cache-misses                                                

      60,604330670 seconds time elapsed

Ukusebenzisa i-CPU Affinity, ukuhlanganiswa ne -march=native, PGO, ukwanda kwenani lokubethelwa i-cache, Nyusa MAX_EVENTS kunye nokusetyenziswa EPOLLET akazange anike ukwanda okukhulu ekusebenzeni. Kodwa kwenzeka ntoni ukuba ukonyusa inani loqhagamshelo ngaxeshanye?

Iinkcukacha-manani ze-352 zoqhagamshelo ngaxeshanye:

$ wrk -c352 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: en-US,en;q=0.5" -H "Connection: keep-alive"
Running 1m test @ http://127.0.0.1:18470
  8 threads and 352 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.12ms    3.79ms  68.23ms   87.49%
    Req/Sec    83.78k    12.69k  169.81k    83.59%
  40006142 requests in 1.00m, 5.48GB read
Requests/sec: 665789.26
Transfer/sec:     93.34MB

Isiphumo esifunwayo sifunyenwe, kwaye kunye negrafu enomdla ebonisa ukuxhomekeka kwenani lezicelo ezicwangcisiweyo kwi-1 ngomzuzu kwinani loqhagamshelwano:

Inomboniso ogcweleyo ongenanto-C I/O reactor

Siyabona ukuba emva kokuqhagamshelwa kwamakhulu ambalwa, inani lezicelo eziqwalaselweyo zazo zombini iiseva lehla kakhulu (kwinguqulelo enemisonto emininzi oku kuphawuleka ngakumbi). Ngaba oku kunxulumene nokuphunyezwa kwesitaki seLinux TCP/IP? Zive ukhululekile ukubhala uqikelelo lwakho malunga nokuziphatha kwegrafu kunye nokulungiswa kweenketho ezinemisonto emininzi kunye nemisonto enye kwizimvo.

njani kuphawuliwe kwizimvo, olu vavanyo lomsebenzi alubonisi ukuziphatha kwe-I/O reactor phantsi kwemithwalo yokwenyani, kuba phantse umncedisi usoloko enxibelelana nesiseko sedatha, iziphumo zelog, isebenzisa i-cryptography nge. TLS njl., ngenxa yoko umthwalo uba yinto engafaniyo (eguquguqukayo). Uvavanyo kunye namacandelo eqela lesithathu luya kuqhutywa kwinqaku malunga ne-I / O proactor.

Ukungalungi kwe-I/O reactor

Kuya kufuneka uqonde ukuba i-reactor ye-I/O ayikho ngaphandle kweengxaki zayo, ezizezi:

  • Ukusebenzisa i-reactor ye-I/O kwindawo enemisonto emininzi kunzima ngakumbi, kuba kuya kufuneka ulawule ngesandla ukuhamba.
  • Uqheliselo lubonisa ukuba kwiimeko ezininzi umthwalo awufani, nto leyo inokukhokelela ekugawulweni kwemisonto enye ngelixa omnye exakeke ngumsebenzi.
  • Ukuba umbambi wesiganeko omnye ubhloka umsonto, umkhethi wenkqubo ngokwawo uya kuvala, nto leyo enokukhokelela kubunzima-ukufumana bugs.

Usombulula ezi ngxaki I/O umdlali weqonga, ehlala inomcwangcisi osasaza ngokulinganayo umthwalo kwichibi leentambo, kwaye ikwane-API elula ngakumbi. Siza kuthetha ngayo kamva, kwelinye inqaku lam.

isiphelo

Kulapho uhambo lwethu ukusuka kwithiyori ngqo ukuya kwiprofayili exhaust lufikelele esiphelweni.

Akufanele uhlale kule nto, kuba zininzi ezinye iindlela ezinomdla ngokulinganayo zokubhala isoftware yenethiwekhi enamanqanaba ahlukeneyo okulula kunye nesantya. Unomdla, ngokombono wam, amakhonkco anikwe ngezantsi.

Ndiza kukubona kungekudala!

Iiprojekthi ezinomdla

Yintoni enye ekufuneka uyifunde?

umthombo: www.habr.com

Yongeza izimvo