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. C lingua et experientiam in retis applicationis progressionem.
Totum codicem in C lingua stricte secundum (cautio: long PDF) ad C11 vexillum pro Linux ac available on GitHub.
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" (epoll/kqueue/IOCP/etc).
Primus aditus implicatus est novum OS linum condere pro unoquoque ineunte nexu. Eius incommodum scalabilitas pauper est: ratio operativa multos efficere debebit transitus context и ratio vocat. Operationes pretiosae sunt et ad penuriam liberi RAM cum infigo numero nexuum ducere possunt.
Mutationem versionem volutpat certum numerum staminum (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 res notitia ratio (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 Acta Vicimediorum Communium eorum complementum. 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 fragmenta advenientis IP facis ut amnis byte (TCP, notitia accepta) vel non erit satis spatii in internis scribere buffers ad subsequentem missionem via nIHIL (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 "software interrupt"). 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 reactor.het exsecutionem - in reactor.c. 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 file descriptor electrix epoll и Nullam tablesGHashTable, Quod maps utraque singula CallbackData (structura eventus tracto et argumentum usoris pro eo).
Quaeso nota quod facultatem tractandi potuimus genus imperfectum iuxta indicem. IN' reactor.h declaramus structuram reactoret in reactor.c definimus, quo minus explicite mutando agros usor. Haec una exemplaria latebras dataquod 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.
Post I/O reactor eventum intercepit cum descriptorem fdvocat tractorem eventum respondentem ad quem transit fd, frenum larva 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.
<КОД СТАТУСА> est numerus effectus operationis repraesentans. Servus noster statum 200 semper reddet (prospera operatione).
<ОПИСАНИЕ СТАТУСА> - linea repraesentatio status code. Nam status code CC hoc OK.
<ЗАГОЛОВОК N> - header of the same format as in the request. Titulos reddemus Content-Length (Lima mole) et Content-Type: text/html (Reditus notitia genus).
<ДАННЫЕ> - data petita a utente. In nobis, haec via est ad imaginem HTML.
lima http_server.c (Uno servo filo perforat) includit file common.hquae 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 * printf()) Et cum codice progressio terminatur EXIT_FAILURE:
munus new_server() refert tabella descriptor "servo" nervum creatum a systema vocat socket(), bind() и listen() et capaces hospites advenientes in modo non-obturbant.
Nota quod nervus initio creatus est in modo non-obstructionis utendi vexillum SOCK_NONBLOCKut in munere on_accept() (Lege magis) ratio vocationis accept() nec cessavit stamina supplicium.
si reuse_port quod true, hoc munus nervum cum optione configurabit SO_REUSEPORT propter setsockopt()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.
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 close().
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().
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.
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 http://127.0.0.1:18470 in pasco et vide quid expectamus;
Finem unius servi metiamur. Duo terminales aperiamus: in uno curremus ./http_serverAliter - wrk. Post minutum, sequentia statistica in secundo termino demonstrabitur:
Unius-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;
Nota quod munus argumentum new_server() actus true. Id est quod optionem servo nervum assignamus SO_REUSEPORTuti in multi- plicata environment. Plura legere potes hic.
Secundo run
Nunc metiamur observantiam multi-filae servientis:
Numerus petitionum discursum in 1 momento augetur ab ~3.28 temporibus! Sed tantum ~XNUMX milliones numeri rotundi deficiebant, sic illud statuere conemur.
Uti CPU affinitas, compilationem cum -march=native, PGOaucto numero hits cache, augere MAX_EVENTS et utere EPOLLET notabile incrementum in perficientur non dedit. Sed quid fit si coniunctionum simul numerum augeas?
Optatus effectus consecutus est, et cum eo graphio interesting ostendens dependentiam numerorum petitionum discursuum in 1 minuto numero nexuum:
Videmus post duos centum nexus, numerum processuum petitionum utriusque servientium acriter (in versione multi-strato hoc magis notabile). Estne hoc cognatum Linux TCP/IP acervum exsecutionem? Liberum est tibi scribere principia tua de hac graphi moribus et optimizations pro multi-filatis et simplicibus sequelis in comment.
How attendendum 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 TLS 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 I / O proactorqui 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.