
introduction
(Uno staminea ) Forma scribendi est summus programmatis onus, in multis solutionibus popularibus adhibitum:
- ...
In hoc articulo spectabimus ins et notas I/O reactoris et quomodo operatur, exsecutionem in minus quam CC lineas codicis scribe, et processum HTTP servo simplici per 200 decies petitiones/min efficiemus.
praefatio
- Articulus scriptus est ad auxilium intellegendum operationem I/O reactoris, ideoque periculum intellige cum ea utens.
- Articulus ad cognitionem elementorum intelligendam requiritur. et experientiam in retis applicationis progressionem.
- Totum codicem in C lingua stricte secundum (cautio: long PDF) ad Linux et praesto est in .
Quid faciam?
Crescente interreti favore, ministri telae magnum numerum nexuum eodem tempore tractare coeperunt, quapropter duo aditus temptati sunt: I/O in magno numero filorum OS interclusio et non-I/O coniunctim cum notificatio systematis eventus, etiam vocatur "ratio electrix" (///etc).
Primus aditus implicatus est novum OS linum condere pro unoquoque ineunte nexu. Eius incommodum scalabilitas pauper est: ratio operativa multos efficere debebit и . Operationes pretiosae sunt et ad penuriam liberi RAM cum infigo numero nexuum ducere possunt.
Mutationem versionem volutpat (filum piscinae), quo systema abortus exsecutionis impeditur, sed simul novam quaestionem introducit: si linum stagnum in praesenti operationibus lectitis impeditum est, deinde aliae bases quae iam datas accipere non poterunt. facere.
Secundus usus accessus (ratio electrix) provisum est a OS. Articulus hic maxime commune genus systematis selectoris agit, secundum summis (eventis, notificationibus) de promptu erga I/O operationes, quam in . Simplex exemplum usus sui per sequentis tabulae stipitem repraesentari potest:

Discrimen inter has aditus talis est;
- Clausus EGO / O res suspendendi user fluxus donecusque ad OS est bene advenientis ut amnis byte (, notitia accepta) vel non erit satis spatii in internis scribere buffers ad subsequentem missionem via (mitto notitia).
- Ratio electrix trans tempus notificat progressio quod OS iam defragmented IP packets (TCP, data receptione) vel satis spatii in internis scribe buffers iam praesto (mitto notitia).
Ad summam, reservato filo OS pro singulis I/O computandi potestatem superfluum est, quia re vera stamina non faciunt opus utile (unde vocabulum ). Systema electrix hoc problema solvit, permittens programmate utentis opibus CPU multo magis oeconomice utendi.
I / O reactor exemplar
Hoc I/O reactor iacuit inter electorem systematis et codicem usoris iacuit. Principium operationis eius ex hoc schemate scandalo describitur:

- Admoneam te rem notificationem esse quamdam nervum nervum non-I/O operationem praestare posse.
- Eventus tracto munus est quod "I/O reactor" vocatur cum eventus recipitur, qui tunc operationem non interclusit I/O exercet.
Illud notandum est quod I/O reactor definitione simplicium liciatorum est, sed nihil prohibet conceptum ab usu in multi- bili ambitu ad rationem 1 sequelae: 1 reactor, inde redivivus omnes CPU coros.
Реализация
Publicum interface in lima ponemus et exsecutionem - in . reactor.h constent sequentia nuntiata:
Ostende declarationes 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);In I / o structuram reactor est electrix и , Quod maps utraque singula CallbackData (structura eventus tracto et argumentum usoris pro eo).
Ostende Reactor et CallbackData
struct reactor {
int epoll_fd;
GHashTable *table; // (int, CallbackData)
};
typedef struct {
Callback callback;
void *arg;
} CallbackData;Quaeso nota quod facultatem tractandi potuimus iuxta indicem. IN' reactor.h declaramus structuram reactoret in reactor.c definimus, quo minus explicite mutando agros usor. Haec una exemplaria quod in C semanticis succincte quadrat.
munera reactor_register, reactor_deregister и reactor_reregister renovare indicem amarum usuris et eventus respondentes tractatores in systemate selectoris et mensae detrahendae.
Monstra adnotatione munera
#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;
}Post I/O reactor eventum intercepit cum descriptorem fdvocat tractorem eventum respondentem ad quem transit fd, generatae certe et a user monstratorem void.
monstrare reactor_run() function
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;
}Ut brevissime dicam, catena functionis in codice usoris vocato formam sequentem capiet:

Una server liciatum
Ut probemus I/O reactor sub alto onere, scribemus simplici HTTP servo interretiali quod cuilibet rogationi cum imagine respondet.
A velox ad HTTP protocol
- hoc est protocol , praesertim pro commercio servo-pasco.
HTTP potest facile usus est protocol Missis et acceptis nuntiis in forma certa .
Forma petitionem
<КОМАНДА> <URI> <ВЕРСИЯ HTTP>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ>CRLFordo est duorum ingenia;rиnprimam aciem rogationis, capitis et data.<КОМАНДА>- unumCONNECT,DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,TRACE. Mandatum navigatoris nostro servo mittetGETid est "mitte mihi tabella contenta."<URI>- . Exempli gratia, si URI =/index.htmltum summa situs petit clientis pagina.<ВЕРСИЯ HTTP>- versio HTTP protocollo in formaHTTP/X.Y. Plerumque adsuesco assuesco version est hodieHTTP/1.1.<ЗАГОЛОВОК N>est clavem-valorem par in forma<КЛЮЧ>: <ЗНАЧЕНИЕ>missis ministris ad ulteriora analysis.<ДАННЫЕ>- notitia requiritur a servo ad operationem exercendam. Saepe suus 'simplex vel aliqua alia forma.
Forma responsio
<ВЕРСИЯ HTTP> <КОД СТАТУСА> <ОПИСАНИЕ СТАТУСА>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ><КОД СТАТУСА>est numerus effectus operationis repraesentans. Servus noster statum 200 semper reddet (prospera operatione).<ОПИСАНИЕ СТАТУСА>- linea repraesentatio status code. Nam status code CC hocOK.<ЗАГОЛОВОК N>- header of the same format as in the request. Titulos reddemusContent-Length(Lima mole) etContent-Type: text/html(Reditus notitia genus).<ДАННЫЕ>- data petita a utente. In nobis, haec via est ad imaginem .
lima (Uno servo filo perforat) includit file quae sequuntur prototypa functionis continet:
Munus monstrare prototypa in 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);Etiam eget tortor dictum SAFE_CALL() et munus definitur fail(). tortor valorem locutionis cum errore comparat, et, si vera conditio est, munus vocat fail():
#define SAFE_CALL(call, error)
do {
if ((call) == error) {
fail("%s", #call);
}
} while (false)munus fail() Transierunt rationes ad procer terminum (sicut * ) Et cum codice progressio terminatur 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);
}munus new_server() refert tabella descriptor "servo" nervum creatum a systema vocat , и et capaces hospites advenientes in modo non-obturbant.
Monstrare new_server() function
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;
}- Nota quod nervus initio creatus est in modo non-obstructionis utendi vexillum
SOCK_NONBLOCKut in munereon_accept()(Lege magis) ratio vocationisaccept()nec cessavit stamina supplicium. - si
reuse_portquodtrue, hoc munus nervum cum optione configurabit propter ut eodem portu utamur in ambitu multi-filato (vide sectionem "Multi-filam servientis").
Event Handler on_accept() vocatus OS generat eventus EPOLLINhoc in casu significatio novi nexus accipi potest. on_accept() novam connexionem accipit, eam permutat ad modum non-obturantis et registra cum eventu tracto on_recv() per I / O reactor.
Ostende on_accept() functionem
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);
}Event Handler on_recv() vocatus OS generat eventus EPOLLINin casu significat nexum descripti on_accept()paratus ad recipiendum data.
on_recv() data ex nexu legit donec petitio HTTP omnino recipitur, deinde tracto registratur on_send() mittere HTTP responsio. Si client nexum frangit, nervus descriptus est et occlusus utens .
Munus monstrare 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);
}
}Event Handler on_send() vocatus OS generat eventus EPOLLOUT, id est nexum descripserunt on_recv()paratus mittere data. Hoc munus mittit responsionem HTTP continentem HTML cum imagine clientis ac deinde eventum tracto ad tergum mutat on_recv().
Ostende on_send() function
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);
}Et tandem in tabella http_server.cIn munus main() nos creare EGO / O reactor usura reactor_new(), servo nervum crea et subcriptio, reactorem utens incipe reactor_run() prorsus enim unum minutum, et tunc facultates emittimus et progressio exit.
Ostende 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);
}Let's check that everything working as expected. Componendis (chmod a+x compile.sh && ./compile.sh in radice project) et sui ipsius scripta servo deducunt, open in pasco et vide quid expectamus;

Euismod measurement
Ostende currus specifications
$ 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
.MMMMMMMMMMMMMMMMMMMFinem unius servi metiamur. Duo terminales aperiamus: in uno curremus ./http_serverAliter - . Post minutum, sequentia statistica in secundo termino demonstrabitur:
$ 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.19MBUnius-filaminis nostri servo processus per 11 decies centena petitiones per minutias ex 100 iunctis oriundis valuit. Non ex malo evenit, sed potest melius?
Multithreaded server
Ut supra, I/O reactor in staminibus separatis creari potest, adhibitis omnibus CPU nucleis. In praxim hanc accedamus;
Ostende 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);
}
}Nunc omne filum reactor:
static Reactor *reactor;
#pragma omp threadprivate(reactor)Nota quod munus argumentum new_server() actus true. Id est quod optionem servo nervum assignamus uti in multi- plicata environment. Plura legere potes .
Secundo run
Nunc metiamur observantiam multi-filae servientis:
$ 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.14MBNumerus petitionum discursum in 1 momento augetur ab ~3.28 temporibus! Sed tantum ~XNUMX milliones numeri rotundi deficiebant, sic illud statuere conemur.
Primum videamus mutant generatae :
$ 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, compilationem cum -march=native, aucto numero hits , augere MAX_EVENTS et utere EPOLLET notabile incrementum in perficientur non dedit. Sed quid fit si coniunctionum simul numerum augeas?
Statistics pro 352 nexus simultaneus;
$ 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.34MBOptatus effectus consecutus est, et cum eo graphio interesting ostendens dependentiam numerorum petitionum discursuum in 1 minuto numero nexuum:

Videmus post aliquot centum conexiones, numerum petitionum tractatarum ab utroque servo acriter decrescere (hoc magis conspicuum est in versione multi-filo). Num hoc ad implementationem pertinet? TCP/IP acervus LinuxSententias tuas de habitu huius graphi et optimisationibus pro versionibus multifilo et unofilo in commentariis libere communica.
How in comment, haec probatio perficiendi mores I/O reactoris sub oneribus realibus non ostendit, quia fere semper ministrans cum datorum, outputorum tigna, cryptographia utitur cum etc., ex quibus onus non uniforme fit. Proactor simul cum tertia factione perficietur in articulo de I/O proactor.
Incommoda EGO / O reactor
Intellegere debes me/O reactor non esse sine eius incommodis, scilicet:
- Usus I/O reactor in ambitu multi-filato paulo difficilior est, quia manually fluxum habebis.
- Exercitatio ostendit in pluribus onus non uniforme esse, quod ad unum colligationem ducere potest, dum alterum in opere occupatum est.
- Si quis eventus tractator filum obstruit, ipsa ratio electrix etiam obstruet, quae cimices duris invenire potest.
Solvit has difficultates qui saepe schedulam habet, quae aequaliter ad piscinam staminum sarcinam distribuit, et API commodiorem habet. De eo postea dicemus, in alio articulo.
conclusio,
Inde est quod iter nostrum a theoria recta in profile exhaustum finem pervenit.
Hoc non est morandum, quia multae aliae sunt aeque interesting accessiones ad programmatum retis scribendi cum diversis gradibus commoditatis et velocitatis. Interest, ut opinor, nexus infra dantur.
Proximum tempus!
Interesting incepta
- B
Quid aliud lego?
Source: www.habr.com
