ሙሉ ባህሪ ያለው ባዶ-ሲ አይ/ኩ ሬአክተር

ሙሉ ባህሪ ያለው ባዶ-ሲ አይ/ኩ ሬአክተር

መግቢያ

I/O ሬአክተር (ነጠላ ክር ክስተት loop) በብዙ ታዋቂ መፍትሄዎቜ ውስጥ ጥቅም ላይ ዹሚውል ኹፍተኛ ጭነት ያለው ሶፍትዌር ለመጻፍ ንድፍ ነው፡

በዚህ ጜሁፍ ውስጥ ዹI/O ሬአክተርን እና እንዎት እንደሚሰራ እንመለኚታለን፣ኚ200 ባነሰ ዚኮድ መስመር ትግበራን እንፅፋለን፣እና ቀላል ዚኀቜቲቲፒ አገልጋይ ሂደት ኹ40 ሚሊዮን በላይ ጥያቄዎቜ/ደቂቃ እናሰራለን።

መቅድም

  • ጜሁፉ ዚተጻፈው ዹ I/O ሬአክተርን ተግባር ለመሚዳት እንዲሚዳ ነው፣ እና ስለዚህ እሱን ሲጠቀሙ ስጋቶቜን ለመሚዳት።
  • ጜሑፉን ለመሚዳት ስለ መሰሚታዊ ነገሮቜ እውቀት ያስፈልጋል. ሐ ቋንቋ እና በአውታሚ መሚብ መተግበሪያ ልማት ውስጥ ዹተወሰነ ልምድ።
  • ሁሉም ኮድ ዹተፃፈው በ C ቋንቋ ነው (በ)ጥንቃቄ: ሹጅም ፒዲኀፍ) ወደ C11 መደበኛ ለሊኑክስ እና በ ላይ ይገኛል። ዹፊልሙ.

ይህ አስፈላጊ ዹሆነው ለምንድን ነው?

ዚበይነመሚብ ተወዳጅነት እዚጚመሚ በመምጣቱ ዚድር አገልጋዮቜ ብዙ ግንኙነቶቜን በአንድ ጊዜ ማስተናገድ ጀመሩ ፣ እና ስለሆነም ሁለት አቀራሚቊቜ ተሞክሹዋል- I/Oን በበርካታ ዚስርዓተ ክወና ክሮቜ ላይ ማገድ እና I/Oን ኹ ጋር በማጣመር ዚክስተት ማሳወቂያ ስርዓት፣ እንዲሁም “ዚስርዓት መራጭ” ተብሎም ይጠራል (epoll/kqueue/አይኊሲፒ/ወዘተ)።

ዚመጀመሪያው አቀራሚብ ለእያንዳንዱ ገቢ ግንኙነት አዲስ ስርዓተ ክወና መፍጠርን ያካትታል። ጉዳቱ ደካማ መጠነ-ሰፊ ነው-ስርዓተ ክወናው ብዙዎቜን መተግበር አለበት። ዚአውድ ሜግግሮቜ О ዚስርዓት ጥሪዎቜ. ውድ ኊፕሬሜኖቜ ናቾው እና በሚያስደንቅ ዚግንኙነት ብዛት ወደ ነፃ ራም እጥሚት ሊያመራ ይቜላል።

ዚተሻሻለው እትም ያደምቃል ቋሚ ዚክሮቜ ብዛት (ክር ገንዳ) ፣ በዚህም ስርዓቱ እንዳይበላሜ ይኹላኹላል ፣ ግን በተመሳሳይ ጊዜ አዲስ ቜግርን ያስተዋውቃል-ዹክር ገንዳ በአሁኑ ጊዜ በሹጅም ንባብ ክዋኔዎቜ ኚታገደ ፣ ኚዚያ ቀደም ሲል መሹጃን መቀበል ዚሚቜሉ ሌሎቜ ሶኬቶቜ ማድሚግ አይቜሉም። ስለዚህ.

ሁለተኛው ዘዮ ይጠቀማል ዚክስተት ማሳወቂያ ስርዓት (ዚስርዓት መራጭ) በ OS ዚቀሚበ። ይህ መጣጥፍ በጣም ዹተለመደውን ዚስርዓት መምሚጫ አይነት ያብራራል፣ ኚማንቂያዎቜ (ክስተቶቜ፣ ማሳወቂያዎቜ) ጋር ስለመዘጋጀት ለአይ/ኩ ኊፕሬሜኖቜ ኹመሆን ይልቅ። ስለ ማጠናቀቃቾው ማሳወቂያዎቜ. ዹአጠቃቀም ቀላል ምሳሌ በሚኹተለው ዹማገጃ ንድፍ ሊወኹል ይቜላል።

ሙሉ ባህሪ ያለው ባዶ-ሲ አይ/ኩ ሬአክተር

በእነዚህ ዘዎዎቜ መካኚል ያለው ልዩነት እንደሚኚተለው ነው.

  • ዹI/O ስራዎቜን ማገድ ማገድ ዹተጠቃሚ ፍሰት ድሚስስርዓተ ክወናው በትክክል እስኪሰራ ድሚስ ማበላሞት ገቢ ዹአይፒ ፓኬቶቜ ወደ ባይት ዥሚት (TCP፣ መሹጃ መቀበል) ወይም ለቀጣይ በመላክ ለመላክ በውስጥ መጻፊያ ቋቶቜ ውስጥ በቂ ቊታ አይኖርም። ምንም ነገር (መሹጃ በመላክ ላይ)።
  • ዚስርዓት መራጭ ተጚማሪ ሰአት ስርዓተ ክወናው ለፕሮግራሙ ያሳውቃል ገና ዚተበላሹ ዹአይፒ ጥቅሎቜ (TCP፣ ዚውሂብ መቀበያ) ወይም በውስጣዊ መጻፊያ ቋቶቜ ውስጥ በቂ ቊታ ገና ይገኛል (ውሂብ መላክ).

ለማጠቃለል ያህል ለእያንዳንዱ I/O ዚስርዓተ ክወናን ክር ማቆዚት ዚኮምፒዩተር ሃይልን ማባኚን ነው፣ ምክንያቱም እንደ እውነቱ ኹሆነ ክሮቹ ጠቃሚ ስራዎቜን እዚሰሩ አይደሉም (ስለዚህ ቃሉ "ዚሶፍትዌር ማቋሚጥ"). ዚስርዓት መምሚጫው ይህንን ቜግር ይፈታል, ዹተጠቃሚው ፕሮግራም ዚሲፒዩ ሀብቶቜን ዹበለጠ በኢኮኖሚ እንዲጠቀም ያስቜለዋል.

I/O ሬአክተር ሞዮል

ዹ I/O ሬአክተር በስርዓት መራጭ እና በተጠቃሚ ኮድ መካኚል እንደ ንብርብር ሆኖ ይሰራል። ዚአሠራሩ መርህ በሚኹተለው ዹማገጃ ንድፍ ተገልጿል.

ሙሉ ባህሪ ያለው ባዶ-ሲ አይ/ኩ ሬአክተር

  • አንድ ክስተት አንድ ዹተወሰነ ሶኬት ዚማያግድ ዹአይ/ኩ ኊፕሬሜን ማኹናወን እንደሚቜል ማሳወቂያ መሆኑን ላስታውስዎ።
  • ዚክስተት ተቆጣጣሪ አንድ ክስተት ሲደርስ በ I/O ሬአክተር ዚሚጠራ ተግባር ነው፣ ኚዚያም ዚማያግድ ዹI/O ክወናን ያኚናውናል።

ዹ I/O ሬአክተር በትርጉሙ ነጠላ-ክር መሆኑን ልብ ማለት ያስፈልጋል፣ ነገር ግን ሀሳቡ ባለብዙ-ክር አካባቢ በ1 ክር ጥምርታ 1 ሬአክተር፣ በዚህም ሁሉንም ዚሲፒዩ ኮሮቜ እንደገና ጥቅም ላይ ኹማዋል ዚሚያግደው ምንም ነገር ዚለም።

ትግበራ

ይፋዊ በይነገጹን በፋይል ውስጥ እናስቀምጣለን። reactor.h, እና ትግበራ - ውስጥ reactor.c. reactor.h ዚሚኚተሉትን ማስታወቂያዎቜ ያካትታል:

መግለጫዎቜን በ reactor.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);

ዹ I/O ሬአክተር መዋቅር ያካትታል ዹፋይል ገላጭ መራጭ epoll О ዚሃሜ ጠሚጎዛዎቜ GHashTable, እያንዳንዱን ሶኬት ዹሚይዝ CallbackData (ዚክስተት ተቆጣጣሪ መዋቅር እና ለእሱ ዹተጠቃሚ ክርክር)።

Reactor እና CallbackData አሳይ

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

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

እባክዎን ዚመቆጣጠር ቜሎታን እንደነቃን ያስተውሉ ያልተሟላ ዓይነት በመሹጃ ጠቋሚው መሠሚት. ውስጥ reactor.h አወቃቀሩን እናውጃለን reactorእና ውስጥ reactor.c እኛ እንገልጻለን, በዚህም ተጠቃሚው መስኮቹን በግልጜ እንዳይቀይር እንኚለክላለን. ይህ ኚስርዓተ-ጥለት አንዱ ነው። መሹጃን መደበቅ፣ ኹ C ትርጉም ጋር በትክክል ዚሚስማማ።

ተግባሮቜ reactor_register, reactor_deregister О reactor_reregister በስርዓት መራጭ እና በሃሜ ሠንጠሚዥ ውስጥ ዚፍላጎት ሶኬቶቜን እና ተጓዳኝ ዚክስተት ተቆጣጣሪዎቜን ዝርዝር ያዘምኑ።

ዚምዝገባ ተግባራትን አሳይ

#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;
}

ዹ I/O ሬአክተር ክስተቱን ኚገለጻው ጋር ካጠለፈ በኋላ fd፣ ዚሚያልፍበትን ተጓዳኝ ዚክስተት ተቆጣጣሪ ይጠራል fd, ቢት ጭምብል ዚመነጩ ክስተቶቜ እና ዹተጠቃሚ ጠቋሚ ወደ void.

ዹ reactor_run() ተግባርን አሳይ

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;
}

ለማጠቃለል በተጠቃሚ ኮድ ውስጥ ያለው ዚተግባር ጥሪዎቜ ሰንሰለት ዹሚኹተለውን ቅጜ ይወስዳል።

ሙሉ ባህሪ ያለው ባዶ-ሲ አይ/ኩ ሬአክተር

ነጠላ ክር አገልጋይ

በኹፍተኛ ጭነት ውስጥ ያለውን ዹአይ/ኩ ሬአክተርን ለመሞኚር፣ ማንኛውንም ጥያቄ በምስል ዚሚመልስ ቀላል ዚኀቜቲቲፒ ዌብ ሰርቹር እንጜፋለን።

ለኀቜቲቲፒ ፕሮቶኮል ፈጣን ማጣቀሻ

HTTP - ይህ ፕሮቶኮል ነው ዚመተግበሪያ ደሚጃበዋናነት ለአገልጋይ-አሳሜ መስተጋብር ያገለግላል።

HTTP በቀላሉ ጥቅም ላይ ሊውል ይቜላል። መጓጓዣ ፕሮቶኮል TCP፣ በተጠቀሰው ቅርጞት መልዕክቶቜን መላክ እና መቀበል ዝርዝር መግለጫ.

ዚጥያቄ ቅርጞት

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

  • CRLF ዚሁለት ቁምፊዎቜ ቅደም ተኹተል ነው r О n, ዚጥያቄውን ዚመጀመሪያ መስመር, ራስጌዎቜን እና መሚጃዎቜን መለዚት.
  • <КОМАНДА> - አንዱ CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. አሳሹ ወደ አገልጋያቜን ትዕዛዝ ይልካል። GET"ዹፋይሉን ይዘት ላክልኝ" ማለት ነው።
  • <URI> - ወጥ ሀብት መለያ. ለምሳሌ, URI = ኹሆነ /index.html, ኚዚያም ደንበኛው ዚጣቢያውን ዋና ገጜ ይጠይቃል.
  • <ВЕРСИЯ HTTP> - ዚኀቜቲቲፒ ፕሮቶኮል ስሪት በቅርጞቱ HTTP/X.Y. ዛሬ በብዛት ጥቅም ላይ ዹዋለው ስሪት ነው HTTP/1.1.
  • <ЗАГОЛОВОК N> በቅርጞቱ ውስጥ ቁልፍ-እሎት ጥንድ ነው <КЛЮЧ>: <ЗНАЧЕНИЕ>ለተጚማሪ ትንተና ወደ አገልጋዩ ተልኳል።
  • <ДАННЫЕ> - ክዋኔውን ለማኹናወን በአገልጋዩ ዹሚፈለግ ውሂብ። ብዙ ጊዜ ቀላል ነው። JSON ወይም ሌላ ማንኛውም ቅርጞት.

ዚምላሜ ቅርጞት

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

  • <КОД СТАТУСА> ዚቀዶ ጥገናውን ውጀት ዹሚወክል ቁጥር ነው. ዚእኛ አገልጋይ ሁል ጊዜ ሁኔታ 200 (ዚተሳካ ክወና) ይመልሳል።
  • <ОПИСАНИЕ СТАТУСА> - ዚሁኔታ ኮድ ሕብሚቁምፊ ውክልና። ለሁኔታ ኮድ 200 ይህ ነው። OK.
  • <ЗАГОЛОВОК N> - በጥያቄው ውስጥ ካለው ተመሳሳይ ቅርጞት ራስጌ። ርዕሶቜን እንመልሳለን Content-Length (ዹፋይል መጠን) እና Content-Type: text/html (ዚመመለሻ ዚውሂብ አይነት).
  • <ДАННЫЕ> - በተጠቃሚው ዹተጠዹቀ ውሂብ. በእኛ ሁኔታ ፣ ወደ ምስሉ ዚሚወስደው መንገድ ይህ ነው። ኀቜቲኀምኀል.

ፋይል http_server.c (ነጠላ ክር አገልጋይ) ፋይልን ያካትታል common.hዚሚኚተሉትን ዚተግባር ፕሮቶታይፖቜ ዚያዘ፡-

ዚተግባር ፕሮቶታይፕን በጋራ አሳይ.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);

ተግባራዊ ማክሮም ተገልጿል SAFE_CALL() እና ተግባሩ ይገለጻል fail(). ማክሮው ዚገለጻውን ዋጋ ኚስህተቱ ጋር ያወዳድራል, እና ሁኔታው ​​እውነት ኹሆነ, ተግባሩን ይጠራል fail():

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

ሥራ fail() ያለፉትን ነጋሪ እሎቶቜ ወደ ተርሚናል ያትማል (እንደ printf()) እና ፕሮግራሙን በኮዱ ያጠፋል 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);
}

ሥራ new_server() በስርዓት ጥሪዎቜ ዹተፈጠሹውን ዹ"አገልጋይ" ሶኬት ፋይል ገላጭ ይመልሳል socket(), bind() О listen() እና ገቢ ግንኙነቶቜን በማይኹለኹል ሁነታ መቀበል ዚሚቜል።

new_server() ተግባር አሳይ

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;
}

  • ሶኬቱ መጀመሪያ ላይ ባንዲራውን ተጠቅሞ በማገድ ሁነታ ላይ እንደተፈጠሚ ልብ ይበሉ SOCK_NONBLOCKስለዚህ በተግባሩ ውስጥ on_accept() (ተጚማሪ ያንብቡ) ዚስርዓት ጥሪ accept() ዹክር መገደሉን አላቆመም።
  • ኹሆነ reuse_port እኩል ነው። true, ኚዚያ ይህ ተግባር ሶኬቱን ኚአማራጭ ጋር ያዋቅሚዋል SO_REUSEPORT በኩል setsockopt()ተመሳሳዩን ወደብ ባለብዙ-ክር አካባቢ ለመጠቀም (“ባለብዙ-ክር አገልጋይ” ክፍልን ይመልኚቱ)።

ዚክስተት ተቆጣጣሪ on_accept() OS አንድ ክስተት ካመነጚ በኋላ ይጠራል EPOLLIN, በዚህ ጉዳይ ላይ አዲሱ ግንኙነት ተቀባይነት ሊኖሹው ይቜላል. on_accept() አዲስ ግንኙነትን ይቀበላል፣ ወደ አለማገድ ሁነታ ይቀይሹዋል እና በክስተት ተቆጣጣሪ ይመዘግባል on_recv() በ I/O reactor ውስጥ።

on_ተቀበል() ተግባር አሳይ

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);
}

ዚክስተት ተቆጣጣሪ on_recv() OS አንድ ክስተት ካመነጚ በኋላ ይጠራል EPOLLIN, በዚህ ጉዳይ ላይ ግንኙነቱ ተመዝግቧል ማለት ነው on_accept(), ውሂብ ለመቀበል ዝግጁ.

on_recv() ዚኀቜቲቲፒ ጥያቄ ሙሉ በሙሉ እስኪቀበል ድሚስ ኚግንኙነቱ ዹተገኘውን መሹጃ ያነባል፣ ኚዚያ ተቆጣጣሪ ይመዘግባል on_send() ዚኀቜቲቲፒ ምላሜ ለመላክ። ደንበኛው ግንኙነቱን ካቋሚጠ, ሶኬቱ ይሰሹዛል እና ተጠቅሞ ይዘጋል close().

ተግባር በ_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);
    }
}

ዚክስተት ተቆጣጣሪ on_send() OS አንድ ክስተት ካመነጚ በኋላ ይጠራል EPOLLOUTግንኙነቱ ተመዝግቧል ማለት ነው። on_recv(), ውሂብ ለመላክ ዝግጁ. ይህ ተግባር ኀቜቲቲኀምኀልን ዚያዘ ምስል ለደንበኛው ይልካል እና ኚዚያ ዚክስተት ተቆጣጣሪውን ወደ ኋላ ይለውጠዋል on_recv().

on_send() ተግባር አሳይ

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);
}

እና በመጚሚሻም, በፋይሉ ውስጥ http_server.c, በተግባር main() በመጠቀም I/O reactor እንፈጥራለን reactor_new()ዹአገልጋይ ሶኬት ይፍጠሩ እና ይመዝገቡ ፣ ሬአክተሩን በመጠቀም ይጀምሩ reactor_run() በትክክል ለአንድ ደቂቃ ያህል, እና ኚዚያም መርጃዎቜን እንለቅቃለን እና ኚፕሮግራሙ እንወጣለን.

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);
}

ሁሉም ነገር እንደተጠበቀው እዚሰራ መሆኑን እንፈትሜ። ማጠናቀር (chmod a+x compile.sh && ./compile.sh በፕሮጀክት ስር) እና በራስ ዚተጻፈውን አገልጋይ ያስጀምሩ, ይክፈቱ http://127.0.0.1:18470 በአሳሹ ውስጥ እና እኛ ዹጠበቅነውን ይመልኚቱ-

ሙሉ ባህሪ ያለው ባዶ-ሲ አይ/ኩ ሬአክተር

ዚአፈጻጞም መለኪያ

ዚመኪናዬን መመዘኛዎቜ አሳይ

$ 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

ዚአንድ ነጠላ ክር አገልጋይ አፈጻጞምን እንለካ። ሁለት ተርሚናሎቜን እንክፈት: በአንደኛው ውስጥ እንሮጣለን ./http_server፣ በተለዹ - wrk. ኚአንድ ደቂቃ በኋላ፣ ዹሚኹተለው ስታቲስቲክስ በሁለተኛው ተርሚናል ላይ ይታያል፡

$ 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

ነጠላ-ክር ያለው አገልጋያቜን በደቂቃ ኹ11 ግንኙነቶቜ ዚመጡ ኹ100 ሚሊዮን በላይ ጥያቄዎቜን ማስተናገድ ቜሏል። መጥፎ ውጀት አይደለም, ግን ሊሻሻል ይቜላል?

ባለብዙ-ክር አገልጋይ

ኹላይ እንደተገለፀው ዹ I/O reactor በተለዹ ክሮቜ ውስጥ ሊፈጠር ይቜላል, በዚህም ሁሉንም ዚሲፒዩ ኮርሶቜ ይጠቀማል. ይህን አካሄድ ወደ ተግባር እናውለው፡-

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);
    }
}

አሁን እያንዳንዱ ክር ዚራሱ ባለቀት ነው። ሬአክተር

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

እባክዎ ዚተግባር ክርክር መሆኑን ልብ ይበሉ new_server() ተሟጋ቟ቜ true. ይህ ማለት ምርጫውን ለአገልጋዩ ሶኬት እንመድባለን። SO_REUSEPORTባለ ብዙ ክር አካባቢ ውስጥ ለመጠቀም. ተጚማሪ ዝርዝሮቜን ማንበብ ይቜላሉ እዚህ.

ሁለተኛ ሩጫ

አሁን ዚባለብዙ-ክር አገልጋይ አፈጻጞምን እንለካ።

$ 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

በ1 ደቂቃ ውስጥ ዹተኹናወኑ ዚጥያቄዎቜ ብዛት ~3.28 ጊዜ ጚምሯል! እኛ ግን ኚክብ ቁጥሩ ~ XNUMX ሚሊዮን ብቻ ነበር ያጥሚናል፣ ስለዚህ ያንን ለማስተካኚል እንሞክር።

በመጀመሪያ ዹተፈጠሹውን ስታቲስቲክስ እንመልኚት ፐርፍ:

$ 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

ዹ CPU Affinity በመጠቀም፣ ጋር ዹተቀናበሹ -march=native, PGO, ዚመምታት ብዛት መጹመር መሞጎጫ, መጹመር MAX_EVENTS እና ይጠቀሙ EPOLLET በአፈፃፀም ላይ ኹፍተኛ ጭማሪ አልሰጠም። ግን በአንድ ጊዜ ዚሚገናኙትን ብዛት ኚጚመሩ ምን ይኚሰታል?

ለ 352 በተመሳሳይ ጊዜ ግንኙነቶቜ ስታቲስቲክስ

$ 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

ዹተፈለገውን ውጀት ተገኝቷል ፣ እና በ 1 ደቂቃ ውስጥ ዹተኹናወኑ ጥያቄዎቜ ብዛት በግንኙነቶቜ ብዛት ላይ ጥገኛ መሆኑን ዚሚያሳይ አስደሳቜ ግራፍ።

ሙሉ ባህሪ ያለው ባዶ-ሲ አይ/ኩ ሬአክተር

ኚአንድ ሁለት መቶ ግንኙነቶቜ በኋላ ዚሁለቱም አገልጋዮቜ ዚተስተካኚሉ ጥያቄዎቜ ቁጥር በኹፍተኛ ሁኔታ እዚቀነሰ መሆኑን እናያለን (በባለብዙ ክር ስሪት ውስጥ ይህ ዹበለጠ ዚሚታይ ነው)። ይህ ኚሊኑክስ TCP/IP ቁልል ትግበራ ጋር ዚተያያዘ ነው? ስለዚህ ዚግራፍ ባህሪ እና ለአስተያዚቶቜ ባለብዙ-ክር እና ነጠላ-ክር አማራጮቜ ማመቻ቞ት ዚእርስዎን ግምቶቜ ለመፃፍ ነፃነት ይሰማዎ።

እንዎት ተጠቅሷል በአስተያዚቶቹ ውስጥ ይህ ዹአፈፃፀም ሙኚራ ዹ I / O ሬአክተርን ባህሪ በእውነተኛ ጭነቶቜ ውስጥ አያሳይም ፣ ምክንያቱም ሁል ጊዜ አገልጋዩ ኹመሹጃ ቋቱ ጋር ይገናኛል ፣ ምዝግብ ማስታወሻዎቜን ያወጣል ፣ ምስጠራን ይጠቀማል። TLS ወዘተ, በዚህ ምክንያት ጭነቱ አንድ ወጥ ያልሆነ (ተለዋዋጭ) ይሆናል. ኚሶስተኛ ወገን አካላት ጋር ሙኚራዎቜ ስለ I / O ፕሮፖክተር በሚለው ጜሑፍ ውስጥ ይኹናወናሉ.

ዹ I/O ሬአክተር ጉዳቶቜ

ዹ I/O ሬአክተር ኚድክመቶቹ ውጭ እንዳልሆነ መሚዳት አለቊት፡-

  • ባለብዙ-ክር አካባቢ ውስጥ I/O ሬአክተር መጠቀም በተወሰነ ደሹጃ አስ቞ጋሪ ነው፣ ምክንያቱም ፍሰቶቹን እራስዎ ማስተዳደር አለብዎት.
  • ልምምድ እንደሚያሳዚው በአብዛኛዎቹ ሁኔታዎቜ ሾክሙ ወጥ ያልሆነ ነው, ይህም ወደ አንድ ክር መግባትን ሊያመጣ ይቜላል, ሌላኛው ደግሞ በስራ ዹተጠመደ ነው.
  • አንድ ዚክስተት ተቆጣጣሪ ክርን ኚኚለኚለ፣ ዚስርዓት መራጩ ራሱ እንዲሁ ያግዳል፣ ይህ ደግሞ ለማግኘት አስ቞ጋሪ ዹሆኑ ስህተቶቜን ያስኚትላል።

እነዚህን ቜግሮቜ ይፈታል አይ/ኩ ፕሮአክተር, ብዙውን ጊዜ ጭነቱን ወደ ክሮቜ ገንዳ እኩል ዚሚያኚፋፍል ዹጊዜ መርሐግብር ያለው እና ዹበለጠ ምቹ ኀፒአይ አለው። በሌላኛው ጜሑፌ ውስጥ ስለ እሱ በኋላ እንነጋገራለን.

መደምደሚያ

ኚቲዎሪ በቀጥታ ወደ ፕሮፋይለር ጭስ ማውጫ ጉዟቜን ያበቃው እዚህ ላይ ነው።

በዚህ ላይ ማተኮር ዚለብዎትም ፣ ምክንያቱም ዚኔትወርክ ሶፍትዌሮቜን በተለያዩ ዚም቟ት እና ዚፍጥነት ደሚጃዎቜ ለመፃፍ ሌሎቜ ብዙ እኩል አስደሳቜ አቀራሚቊቜ አሉ። ዹሚገርመው, በእኔ አስተያዚት, አገናኞቜ ኹዚህ በታቜ ተሰጥተዋል.

በቅርቡ እንገናኝ!

አስደሳቜ ፕሮጀክቶቜ

ሌላ ምን ማንበብ አለብኝ?

ምንጭ: hab.com

አስተያዚት ያክሉ