بشپړ ځانګړتیا لرونکی بېئر-C I/O ریکټور

بشپړ ځانګړتیا لرونکی بېئر-C I/O ریکټور

پېژندنه

I/O ریکټور (واحد تار شوی د پیښې لوپ) د لوړ بار سافټویر لیکلو لپاره یوه نمونه ده چې په ډیری مشهور حلونو کې کارول کیږي:

په دې مقاله کې، موږ به د I/O ریکټور دننه او بهر وګورو او دا څنګه کار کوي، د کوډ له 200 څخه لږ لینونو کې پلي کول ولیکئ، او د 40 ملیون غوښتنو / دقیقو څخه ډیر ساده HTTP سرور پروسه جوړه کړئ.

وړاندیز

  • مقاله د I/O ریکټور د فعالیت په پوهیدو کې د مرستې لپاره لیکل شوې وه ، او له همدې امله د کارولو پرمهال خطرونه درک کوي.
  • د مقالې د پوهیدو لپاره د اساساتو پوهه اړینه ده. سي ژبه او د شبکې غوښتنلیک پراختیا کې ځینې تجربه.
  • ټول کوډ په C ژبه کې په کلکه د (احتیاط: اوږد PDF) د C11 معیار ته د لینکس لپاره او شتون لري GitHub.

دا ولې ضروري دی؟

د انټرنیټ د مخ په زیاتیدونکي شهرت سره، ویب سرورونو په ورته وخت کې د ډیرو اړیکو اداره کولو ته اړتیا پیل کړه، او له همدې امله دوه طریقې هڅه شوې: د I/O په ډیری OS تارونو کې بلاک کول او د I/O سره په ترکیب کې غیر بلاک کول. د پیښې خبرتیا سیسټم چې د "سیسټم ټاکونکی" په نوم هم یادیږي (epoll/کتار/IOCP/etc).

لومړۍ طریقه د هر راتلونکي پیوستون لپاره د نوي OS تار رامینځته کول شامل دي. د دې نیمګړتیا ضعیف پیمانه ده: عملیاتي سیسټم باید ډیری پلي کړي د شرایطو لیږد и سیسټم زنګونه. دا ګران عملیات دي او کولی شي د اغیزمن شمیر اړیکو سره د وړیا رام نشتوالي لامل شي.

تعدیل شوی نسخه روښانه کوي د تارونو ثابت شمیر (thread pool)، په دې توګه د سیسټم د اجرا کولو څخه مخنیوی کوي، مګر په ورته وخت کې یوه نوې ستونزه معرفي کوي: که چیرې د تار حوض دا مهال د اوږد لوستلو عملیاتو لخوا بند شوی وي، نو نور ساکټونه چې دمخه یې د معلوماتو ترلاسه کولو توان نلري. داسې وکړئ.

دویمه طریقه کارول کیږي د پیښې خبرتیا سیسټم (سیسټم ټاکونکی) د OS لخوا چمتو شوی. دا مقاله د I/O عملیاتو لپاره د چمتووالي په اړه د خبرتیاو (پیښو ، خبرتیاو) پراساس د سیسټم انتخاب کونکي خورا عام ډول په اړه بحث کوي. د دوی د بشپړیدو په اړه خبرتیاوې. د دې کارولو ساده مثال د لاندې بلاک ډیاګرام لخوا نمایش کیدی شي:

بشپړ ځانګړتیا لرونکی بېئر-C I/O ریکټور

د دې طریقو ترمنځ توپیر په لاندې ډول دی:

  • د I/O عملیات بندول ځنډول د کارونکي جریان تر څوتر هغه چې OS په سمه توګه وي تخریبونه راتلونکی د IP کڅوړې د بایټ جریان ته (TCP، د معلوماتو ترلاسه کول) یا به د داخلي لیکلو بفرونو کې د راتلونکي لیږلو لپاره کافي ځای شتون ونلري. NIC (د معلوماتو لیږل).
  • سیسټم ټاکونکی پس له وخته پروګرام ته خبر ورکوي چې OS لا د ډیفرګمینټ شوي IP پاکټونه (TCP، د معلوماتو رسیدنه) یا د داخلي لیکلو بفرونو کې کافي ځای لا د شتون لري (د معلوماتو لیږل).

د دې لنډیز لپاره ، د هر I/O لپاره د OS تار ساتل د کمپیوټري ځواک ضایع کول دي ، ځکه چې په حقیقت کې تارونه ګټور کار نه کوي (له همدې امله اصطلاح "د سافټویر خنډ"). د سیسټم انتخاب کونکی دا ستونزه حل کوي، د کاروونکي پروګرام ته اجازه ورکوي چې د CPU سرچینې خورا ډیر اقتصادي کاروي.

د I/O ریکټور ماډل

I/O ریکټر د سیسټم انتخاب کونکي او د کارونکي کوډ تر مینځ د پرت په توګه کار کوي. د دې عملیاتو اصول د لاندې بلاک ډیاګرام لخوا تشریح شوي:

بشپړ ځانګړتیا لرونکی بېئر-C I/O ریکټور

  • اجازه راکړئ تاسو ته یادونه وکړم چې پیښه یو خبرتیا ده چې یو ځانګړی ساکټ د غیر بلاک کولو I/O عملیات ترسره کولو توان لري.
  • د پیښې هینډلر یو فنکشن دی چې د I/O ریکټر لخوا ویل کیږي کله چې پیښه ترلاسه شي ، کوم چې بیا د I/O غیر بلاک کولو عملیات ترسره کوي.

دا مهمه ده چې په یاد ولرئ چې I/O ریکټور د تعریف له مخې واحد - تار شوی دی، مګر هیڅ شی شتون نلري چې مفهوم د 1 سلیډ: 1 ریکتور په تناسب په څو-تریډ شوي چاپیریال کې د کارولو مخه ونیسي، په دې توګه د CPU ټول کورونه ریسایکل کوي.

پلي کول

موږ به عامه انٹرفیس په فایل کې ځای په ځای کړو 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 (د پیښې اداره کونکي جوړښت او د دې لپاره د کارونکي دلیل).

ریکټر او د کال بیک ډیټا وښایاست

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

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

مهرباني وکړئ په یاد ولرئ چې موږ د سمبالولو وړتیا فعاله کړې ده نامکمل ډول د شاخص له مخې. IN 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;
}

د لنډیز کولو لپاره، د کارن کوډ کې د فنکشن زنګونو سلسله به لاندې بڼه واخلي:

بشپړ ځانګړتیا لرونکی بېئر-C I/O ریکټور

واحد تار شوی سرور

د لوړ بار لاندې د I/O ریکټر ازموینې لپاره ، موږ به یو ساده HTTP ویب سرور ولیکو چې د عکس سره هرې غوښتنې ته ځواب ووایی.

د HTTP پروتوکول ته چټک حواله

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، بیا پیرودونکي د سایټ اصلي پا pageې غوښتنه کوي.
  • <ВЕРСИЯ HTTP> - په بڼه کې د 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 (د معلوماتو ډول بیرته راګرځي).
  • <ДАННЫЕ> - د کارونکي لخوا غوښتل شوي معلومات. زموږ په قضیه کې، دا د انځور لپاره لاره ده د HTML.

د دوتنې http_server.c (واحد تاریډ سرور) کې فایل شامل دی common.h، کوم چې د لاندې فعالیت پروټوټایپونه لري:

په common.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() او په غیر بلاک کولو حالت کې د راتلونکو اړیکو منلو وړ.

نوی_سرور () فعالیت وښایاست

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 ریکټور کې.

on_accept() فنکشن وښایاست

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() د پیوستون څخه ډاټا لوستل تر هغه چې د HTTP غوښتنه په بشپړه توګه ترلاسه نشي، بیا دا یو سمبالونکی راجستر کوي on_send() د HTTP ځواب لیږلو لپاره. که چیرې پیرودونکي پیوستون مات کړي، ساکټ یې راجستر شوی او په کارولو سره تړل شوی 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()د معلوماتو لیږلو ته چمتو دی. دا فنکشن پیرودونکي ته د عکس سره HTML لرونکی HTTP ځواب لیږي او بیا د پیښې اداره کونکي بیرته ته بدلوي 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_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 په براوزر کې او وګورئ چې موږ څه تمه درلوده:

بشپړ ځانګړتیا لرونکی بېئر-C I/O ریکټور

د فعالیت اندازه کول

زما د موټر مشخصات وښایاست

$ 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 -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 ریکټر په جلا تارونو کې رامینځته کیدی شي، په دې توګه د CPU ټول کورونه کاروي. راځئ چې دا طریقه عملي کړو:

وښایاست 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 تړاو کارولسره تالیف -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 دقیقو کې د پروسس شویو غوښتنو شمیر پورې تړاو ښیي:

بشپړ ځانګړتیا لرونکی بېئر-C I/O ریکټور

موږ ګورو چې د څو سوو ارتباطاتو وروسته ، د دواړو سرورونو لپاره د پروسس شوي غوښتنو شمیر په چټکۍ سره راټیټیږي (په څو اړخیزه نسخه کې دا خورا د پام وړ دی). ایا دا د لینکس TCP/IP سټیک پلي کولو پورې اړه لري؟ د ګراف د دې چلند په اړه خپل انګیرنې ولیکئ او په نظرونو کې د څو تارونو او واحد تارونو اختیارونو لپاره اصلاح کولو لپاره وړیا احساس وکړئ.

څنګه یاد شوی په نظرونو کې، دا د فعالیت ازموینه د ریښتینې بارونو لاندې د I/O ریکټور چلند نه ښیې، ځکه چې نږدې تل سرور د ډیټابیس سره اړیکه نیسي، لاګونه تولیدوي، د کریپټوګرافي څخه کار اخلي. ټي ایل ایس او داسې نور، چې په پایله کې بار غیر یونیفورم (متحرک) کیږي. د دریمې ډلې اجزاو سره یوځای ازموینې به د I/O پراکټر په اړه مقاله کې ترسره شي.

د I/O ریکټور زیانونه

تاسو اړتیا لرئ پوه شئ چې I/O ریکټر د دې نیمګړتیاو پرته نه دی، یعنې:

  • په څو اړخیزه چاپیریال کې د I/O ریکټور کارول یو څه ډیر ستونزمن دی، ځکه چې تاسو باید په لاسي ډول جریان اداره کړئ.
  • تمرین ښیي چې په ډیری مواردو کې بار غیر یونیفورم دی، کوم چې کولی شي د یوې تار د ننوتلو لامل شي پداسې حال کې چې بل په کار بوخت وي.
  • که چیرې د پیښې یو سمبالونکی تار بند کړي ، د سیسټم انتخاب کونکی به پخپله هم بلاک کړي ، کوم چې کولی شي د موندلو سختو بګونو لامل شي.

دا ستونزې حل کوي I/O پراکټر، کوم چې ډیری وختونه مهالویش لري چې په مساوي ډول د تارونو حوض ته بار توزیع کوي ، او یو ډیر مناسب API هم لري. موږ به وروسته په دې اړه خبرې وکړو، زما په بله مقاله کې.

پایلې

دا هغه ځای دی چې زموږ له تیوري څخه مستقیم پروفایلر اخراج ته پای ته رسیدلی.

تاسو باید پدې اړه فکر ونه کړئ ، ځکه چې د اسانتیا او سرعت مختلف کچو سره د شبکې سافټویر لیکلو لپاره ډیری نور مساوي په زړه پوري لارې شتون لري. په زړه پورې، زما په نظر، لینکونه لاندې ورکړل شوي.

ژر به سره ګورو!

په زړه پورې پروژې

نور څه لوستل؟

سرچینه: www.habr.com

Add a comment