Reactair I/O (snàthainn singilte lùb tachartas) na phàtran airson bathar-bog làn luchd a sgrìobhadh, air a chleachdadh ann am mòran fhuasglaidhean mòr-chòrdte:
San artaigil seo, seallaidh sinn ri taobh a-staigh agus taobh a-muigh reactair I / O agus mar a tha e ag obair, sgrìobhaidh sinn buileachadh ann an nas lugha na loidhnichean còd 200, agus nì sinn pròiseas frithealaiche HTTP sìmplidh thairis air 40 millean iarrtas / mion.
Facal-toisich
Chaidh an artaigil a sgrìobhadh gus cuideachadh le bhith a’ tuigsinn gnìomhachd an reactair I/O, agus mar sin a’ tuigsinn nan cunnartan nuair a thathar ga chleachdadh.
Tha feum air eòlas air na bunaitean gus an artaigil a thuigsinn. C cànan agus beagan eòlais ann an leasachadh aplacaidean lìonraidh.
Tha a h-uile còd sgrìobhte ann an cànan C gu teann a rèir (rabhadh: PDF fada) gu ìre C11 airson Linux agus ri fhaighinn air GitHub.
Carson a tha seo riatanach?
Le fàs mòr-chòrdte air an eadar-lìn, thòisich luchd-frithealaidh lìn a’ feumachdainn àireamh mhòr de cheanglaichean a làimhseachadh aig an aon àm, agus mar sin chaidh dà dhòigh-obrach fheuchainn: a’ bacadh I/O air àireamh mhòr de shnàithleanan OS agus gun a bhith a’ bacadh I/O còmhla ri siostam fios tachartais, ris an canar cuideachd “system selector” (epoll/kqueue/IOCP/etc).
B’ e a’ chiad dòigh-obrach a bhith a’ cruthachadh snàithlean OS ùr airson gach ceangal a bha a’ tighinn a-steach. Is e ana-cothrom a th’ ann droch scalability: feumaidh an siostam obrachaidh mòran a chuir an gnìomh eadar-ghluasadan co-theacsa и gairmean siostam. Tha iad nan obraichean daor agus faodaidh iad leantainn gu dìth RAM an-asgaidh le àireamh iongantach de cheanglaichean.
Tha an dreach atharraichte a’ nochdadh àireamh stèidhichte de snàithleanan (amar snàthainn), mar sin a’ cur casg air an t-siostam bho bhith a’ cur gu bàs, ach aig an aon àm a’ toirt a-steach duilgheadas ùr: ma tha amar snàithlean air a bhacadh an-dràsta le gnìomhachd leughaidh fada, cha bhith e comasach dha socaidean eile a tha comasach air dàta fhaighinn mu thràth. dèan sin.
Tha an dàrna dòigh-obrach a 'cleachdadh siostam fios tachartais (roghnaiche siostam) air a sholarachadh leis an OS. Tha an artaigil seo a’ beachdachadh air an t-seòrsa roghnaiche siostam as cumanta, stèidhichte air rabhaidhean (tachartasan, fiosan) mu cho deònach sa tha obair I/O, seach air fiosan mun chrìochnachadh. Faodar eisimpleir nas sìmplidh de a chleachdadh a riochdachadh leis an diagram bloc a leanas:
Tha an eadar-dhealachadh eadar na dòighean-obrach seo mar a leanas:
A’ bacadh gnìomhachd I/O cuir stad air sruth luchd-cleachdaidh gusgus am bi an OS ceart defragments a' tighinn a-steach IP pacaidean gu sruth baidht (TCP, a’ faighinn dàta) no cha bhi àite gu leòr ri fhaighinn anns na bufairean sgrìobhaidh a-staigh airson an cur troimhe às deidh sin NIC (a 'cur dàta).
Tagraiche siostam Thairis air ùine fios don phrògram gu bheil an OS mar-thà pacaidean IP defragmented (TCP, fàilteachadh dàta) no àite gu leòr ann am bufairean sgrìobhaidh a-staigh mar-thà ri fhaighinn (a’ cur dàta).
Gus geàrr-chunntas a dhèanamh, tha a bhith a’ glèidheadh snàithlean OS airson gach I/O na sgudal air cumhachd coimpiutaireachd, oir ann an da-rìribh, chan eil na snàithleanan a’ dèanamh obair fheumail (mar sin an teirm "briseadh bathar-bog"). Bidh roghnaiche an t-siostaim a’ fuasgladh na duilgheadas seo, a’ leigeil leis a’ phrògram luchd-cleachdaidh goireasan CPU a chleachdadh tòrr nas eaconamaiche.
Modail reactor I/O
Bidh an reactar I / O ag obair mar shreath eadar roghnaichear an t-siostaim agus còd an neach-cleachdaidh. Tha prionnsapal a h-obrachaidh air a mhìneachadh leis an diagram bloc a leanas:
Leig leam do chuimhneachadh gur e fios a th’ ann an tachartas gu bheil socaid sònraichte comasach air gnìomhachd I/O nach eil a’ bacadh a dhèanamh.
Is e gnìomh a th’ ann an làimhseachadh tachartais ris an canar an reactar I/O nuair a gheibhear tachartas, a bhios an uairsin a’ coileanadh gnìomhachd I/O nach eil a’ bacadh.
Tha e cudromach toirt fa-near gu bheil an reactair I / O le mìneachadh aon-snàthainn, ach chan eil dad a ’cur stad air a’ bhun-bheachd bho bhith air a chleachdadh ann an àrainneachd ioma-snàithlean aig co-mheas de 1 snàithlean: 1 reactor, mar sin ag ath-chuairteachadh a h-uile cores CPU.
Реализация
Cuiridh sinn an eadar-aghaidh poblach ann am faidhle reactor.h, agus buileachadh - a-steach reactor.c. reactor.h bidh na sanasan a leanas:
Seall dearbhaidhean ann an 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);
Tha structar an reactar I/O air a dhèanamh suas de tuairisgeul faidhle roghnaiche epoll и bùird hashGHashTable, a tha a’ mapadh gach socaid gu CallbackData (structar neach-làimhseachaidh tachartais agus argamaid neach-cleachdaidh air a shon).
Thoir an aire gu bheil sinn air comas a làimhseachadh seòrsa neo-iomlan a rèir an clàr-amais. ANNS reactor.h bidh sinn a’ cur an cèill an structair reactor, agus a-steach reactor.c bidh sinn ga mhìneachadh, agus mar sin cha leig sinn leis a’ chleachdaiche na raointean aige atharrachadh gu soilleir. Is e seo aon de na pàtranan falach dàta, a tha gu dlùth a’ freagairt ri C semantics.
Feartan reactor_register, reactor_deregister и reactor_reregister ùraich an liosta de socaidean inntinneach agus luchd-làimhseachaidh tachartais co-fhreagarrach ann an roghnaiche an t-siostaim agus clàr hash.
Às deidh don reactair I/O an tachartas a ghlacadh leis an tuairisgeul fd, bidh e a’ gairm an neach-làimhseachaidh tachartais co-fhreagarrach, dha bheil e a’ dol fd, beagan masg tachartasan a chaidh a chruthachadh agus comharra neach-cleachdaidh gu void.
Seall gnìomh 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;
}
Gus geàrr-chunntas a dhèanamh, bidh an t-sreath de ghairmean gnìomh ann an còd cleachdaiche mar a leanas:
Frithealaiche snàthaichte singilte
Gus an reactair I / O a dhearbhadh fo luchd àrd, sgrìobhaidh sinn frithealaiche lìn HTTP sìmplidh a fhreagras iarrtas sam bith le ìomhaigh.
Iomradh sgiobalta air protocol HTTP
HTTP - is e seo am protocol ìre tagraidh, air a chleachdadh gu sònraichte airson eadar-obrachadh frithealaiche-brabhsair.
Faodar HTTP a chleachdadh gu furasta thairis air còmhdhail protocol TCP, a 'cur agus a' faighinn teachdaireachdan ann an cruth a chaidh a shònrachadh sònrachadh.
CRLF tha sreath de dhà charactar ann: r и n, a 'sgaradh a' chiad loidhne den iarrtas, cinn-cinn agus dàta.
<КОМАНДА> - aon de CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Cuiridh am brabhsair àithne chun an fhrithealaiche againn GET, meaning "Cuir thugam susbaint an fhaidhle."
<URI> - aithnichear goireas èideadh. Mar eisimpleir, ma tha URI = /index.html, an uairsin bidh an neach-dèiligidh ag iarraidh prìomh dhuilleag na làraich.
<ВЕРСИЯ HTTP> - dreach den phròtacal HTTP san cruth HTTP/X.Y. Is e an dreach as cumanta an-diugh HTTP/1.1.
<ЗАГОЛОВОК N> 's e paidhir prìomh-luach anns a' chruth <КЛЮЧ>: <ЗНАЧЕНИЕ>, air a chuir chun t-seirbheisiche airson tuilleadh sgrùdaidh.
<ДАННЫЕ> - dàta a dh’ fheumas an t-seirbheisiche gus an obair a choileanadh. Gu tric tha e sìmplidh JSON no cruth sam bith eile.
<КОД СТАТУСА> Is e àireamh a tha a’ riochdachadh toradh na h-obrach. Bidh an frithealaiche againn an-còmhnaidh a’ tilleadh inbhe 200 (obrachadh soirbheachail).
<ОПИСАНИЕ СТАТУСА> - riochdachadh sreang den chòd inbhe. Airson còd inbhe 200 tha seo OK.
<ЗАГОЛОВОК N> - bann-cinn den aon chruth ris an iarrtas. Tillidh sinn na tiotalan Content-Length (meud faidhle) agus Content-Type: text/html (seòrsa dàta tilleadh).
<ДАННЫЕ> - dàta a dh’ iarr an neach-cleachdaidh. Anns a 'chùis againn, is e seo an t-slighe chun an ìomhaigh a-steach HTML.
faidhl http_server.c (frithealaiche snàthaichte singilte) a’ toirt a-steach faidhle common.h, anns a bheil na prototypes gnìomh a leanas:
Seall prototypes gnìomh mar as trice.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);
Tha am macro gnìomh air a mhìneachadh cuideachd SAFE_CALL() agus tha an gnìomh air a mhìneachadh fail(). Bidh am macro a 'dèanamh coimeas eadar luach an abairt leis a' mhearachd, agus ma tha an suidheachadh fìor, canar an gnìomh ris fail():
#define SAFE_CALL(call, error)
do {
if ((call) == error) {
fail("%s", #call);
}
} while (false)
gnìomh fail() a’ clò-bhualadh na h-argamaidean a chaidh seachad chun cheann-uidhe (mar printf()) agus a’ cur crìoch air a’ phrògram leis a’ chòd EXIT_FAILURE:
gnìomh new_server() a’ tilleadh tuairisgeul faidhle an t-socaid “frithealaiche” a chaidh a chruthachadh le fiosan siostam socket(), bind() и listen() agus comasach air gabhail ri ceanglaichean a tha a’ tighinn a-steach ann am modh gun bhacadh.
Thoir an aire gu bheil an socaid air a chruthachadh an toiseach ann am modh neo-bacadh a’ cleachdadh a’ bhratach SOCK_NONBLOCKmar sin anns a' ghnìomh on_accept() (leugh tuilleadh) gairm siostam accept() cha do chuir sin stad air coileanadh an t-snàthainn.
ma reuse_port co-ionann true, an uairsin rèitichidh an gnìomh seo an t-socaid leis an roghainn SO_REUSEPORT troimhe setsockopt()gus an aon phort a chleachdadh ann an àrainneachd ioma-snàthainn (faic an earrann “Frithealaiche ioma-snàithleach”).
Làimhseachadh Tachartas on_accept() air a ghairm às deidh don OS tachartas a ghineadh EPOLLIN, anns a 'chùis seo a' ciallachadh gum faodar gabhail ris a 'cheangal ùr. on_accept() a’ gabhail ri ceangal ùr, ga atharrachadh gu modh neo-bacadh agus a’ clàradh le neach-làimhseachaidh tachartais on_recv() ann an reactar I/O.
Làimhseachadh Tachartas on_recv() air a ghairm às deidh don OS tachartas a ghineadh EPOLLIN, anns a 'chùis seo a' ciallachadh gu bheil an ceangal clàraichte on_accept(), deiseil airson dàta fhaighinn.
on_recv() a 'leughadh dàta bhon cheangal gus an tèid an t-iarrtas HTTP fhaighinn gu tur, an uairsin bidh e a' clàradh inneal-làimhseachaidh on_send() gus freagairt HTTP a chuir. Ma bhriseas an neach-dèiligidh an ceangal, thèid an t-socaid a dhì-chlàradh agus a dhùnadh le bhith a’ cleachdadh close().
Seall gnìomh on_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);
}
}
Làimhseachadh Tachartas on_send() air a ghairm às deidh don OS tachartas a ghineadh EPOLLOUT, a 'ciallachadh gu bheil an ceangal clàraichte on_recv(), deiseil airson dàta a chuir. Bidh an gnìomh seo a’ cur freagairt HTTP anns a bheil HTML le ìomhaigh chun neach-dèiligidh agus an uairsin ag atharrachadh làimhseachadh an tachartais air ais gu on_recv().
Agus mu dheireadh, anns an fhaidhle http_server.c, ann an gnìomh main() bidh sinn a’ cruthachadh reactar I/O a’ cleachdadh reactor_new(), cruthaich socaid frithealaiche agus clàraich e, tòisich an reactair a’ cleachdadh reactor_run() airson dìreach aon mhionaid, agus an uairsin bidh sinn a’ leigeil a-mach goireasan agus a ’fàgail a’ phrògram.
Feuch an dèan sinn cinnteach gu bheil a h-uile càil ag obair mar a bhiodh dùil. A' cur ri chèile (chmod a+x compile.sh && ./compile.sh ann am freumh a’ phròiseict) agus cuir air bhog am frithealaiche fèin-sgrìobhte, fosgail http://127.0.0.1:18470 sa bhrobhsair agus faic na bha sinn an dùil:
Feuch an tomhais sinn coileanadh frithealaiche aon-snàthainn. Fosglamaid dà cheann-uidhe: ann an aon ruithidh sinn ./http_server, ann an caochladh - obair. Às deidh mionaid, thèid na staitistig a leanas a thaisbeanadh san dàrna ceann-uidhe:
B’ urrainn don t-seirbheisiche aon-snàthainn againn còrr air 11 millean iarrtas gach mionaid a phròiseasadh a thàinig bho 100 ceangal. Chan e droch thoradh a th’ ann, ach an gabh a leasachadh?
Frithealaiche ioma-snàthainn
Mar a chaidh ainmeachadh gu h-àrd, faodar an reactair I / O a chruthachadh ann an snàithleanan fa leth, agus mar sin a’ cleachdadh a h-uile cores CPU. Leig leinn an dòigh-obrach seo a chur an gnìomh:
Thoir an aire gu bheil an argamaid gnìomh new_server() tagraichean true. Tha seo a’ ciallachadh gun sònraich sinn an roghainn gu socaid an fhrithealaiche SO_REUSEPORTa chleachdadh ann an àrainneachd ioma-snàthainn. Faodaidh tu barrachd mion-fhiosrachaidh a leughadh an seo.
An dàrna ruith
A-nis tomhais sinn coileanadh frithealaiche ioma-snàithlean:
Chaidh an àireamh de dh’iarrtasan a chaidh a ghiullachd ann an 1 mhionaid suas ~3.28 uair! Ach cha robh sinn ach ~XNUMX mhillean gann air an àireamh chruinn, mar sin feuchaidh sinn ri sin a chàradh.
An toiseach leig dhuinn sùil a thoirt air na staitistig a chaidh a chruthachadh perf:
A 'cleachdadh CPU Affinity, cruinneachadh le -march=native, PGO, àrdachadh anns an àireamh de bhuillean tasgadan, àrdachadh MAX_EVENTS agus cleachdadh EPOLLET cha tug e àrdachadh mòr ann an coileanadh. Ach dè a thachras ma mheudaicheas tu an àireamh de cheanglaichean aig an aon àm?
Staitistig airson 352 ceanglaichean aig an aon àm:
Chaidh an toradh a bhathas ag iarraidh fhaighinn, agus leis graf inntinneach a 'sealltainn an eisimeileachd air an àireamh de dh'iarrtasan pròiseasaichte ann an 1 mionaid air an àireamh de cheanglaichean:
Chì sinn, às deidh dà cheud ceangal, gu bheil an àireamh de dh ’iarrtasan giullachd airson an dà fhrithealaiche a’ tuiteam gu mòr (anns an dreach ioma-snàthainn tha seo nas follaisiche). A bheil seo co-cheangailte ri buileachadh stac Linux TCP/IP? Faodaidh tu do bharailean a sgrìobhadh mun ghiùlan seo den ghraf agus optimizations airson roghainnean ioma-snàthainn agus aon-snàthainn anns na beachdan.
Ciamar thugadh fainear anns na beachdan, chan eil an deuchainn coileanaidh seo a’ sealltainn giùlan an reactair I / O fo luchdan fìor, oir cha mhòr an-còmhnaidh bidh an frithealaiche ag eadar-obrachadh leis an stòr-dàta, logaichean toraidh, a’ cleachdadh cryptography le TLS msaa, agus mar thoradh air an sin bidh an luchd a’ fàs neo-èideadh (fiùghantach). Thèid deuchainnean còmhla ri co-phàirtean treas-phàrtaidh a dhèanamh san artaigil mun proactor I / O.
Eas-bhuannachdan an reactar I/O
Feumaidh tu tuigsinn nach eil an reactar I / O às aonais na h-eas-bhuannachdan aige, is e sin:
Tha e nas duilghe a bhith a’ cleachdadh reactair I/O ann an àrainneachd ioma-snàthainn, oir feumaidh tu na sruthan a riaghladh le làimh.
Tha cleachdadh a 'sealltainn gu bheil an luchd neo-èideadh sa mhòr-chuid, agus faodaidh seo leantainn gu aon snàithlean a' logadh fhad 'sa tha fear eile trang le obair.
Ma chuireas aon neach-làimhseachaidh tachartas casg air snàithlean, cuiridh neach-roghnaiche an t-siostaim fhèin bacadh air cuideachd, agus faodaidh seo leantainn gu mialan a tha doirbh a lorg.
Fuasgail na duilgheadasan sin I/O neach-dearbhaidh, aig a bheil clàr-ama gu tric a bhios a’ sgaoileadh an luchd gu cruinneachadh snàithnean gu cothromach, agus aig a bheil API nas goireasaiche cuideachd. Bruidhnidh sinn mu dheidhinn nas fhaide air adhart, anns an artaigil eile agam.
co-dhùnadh
Seo far a bheil ar turas bho theòiridh dìreach a-steach don inneal-togail profiler air a thighinn gu crìch.
Cha bu chòir dhut fuireach air an seo, oir tha mòran dhòighean-obrach eile ann a tha a cheart cho inntinneach airson bathar-bog lìonra a sgrìobhadh le diofar ìrean de ghoireasachd agus astar. Inntinneach, nam bheachd-sa, tha ceanglaichean air an toirt seachad gu h-ìosal.