آرٽيڪل I/O ري ايڪٽر جي ڪم کي سمجهڻ ۾ مدد ڏيڻ لاءِ لکيو ويو آهي، ۽ تنهن ڪري ان کي استعمال ڪرڻ وقت خطرن کي سمجھو.
مضمون کي سمجھڻ لاءِ بنيادي ڄاڻ جي ضرورت آھي. سي ٻولي ۽ نيٽ ورڪ ايپليڪيشن ڊولپمينٽ ۾ ڪجهه تجربو.
سمورو ڪوڊ سختي سان سي ٻولي ۾ لکيل آهي (احتياط: ڊگهو PDF) C11 معيار تائين لينڪس لاءِ ۽ دستياب تي GitHub.
هي ضروري آهي؟
انٽرنيٽ جي وڌندڙ مقبوليت سان، ويب سرورز کي هڪ ئي وقت وڏي تعداد ۾ ڪنيڪشن سنڀالڻ جي ضرورت محسوس ٿيڻ لڳي، ۽ ان ڪري ٻه طريقا آزمايا ويا: وڏي تعداد ۾ OS ٿريڊز تي I/O کي بلاڪ ڪرڻ ۽ غير بلاڪ ڪرڻ I/O سان ميلاپ ۾. ھڪڙو واقعو نوٽيفڪيشن سسٽم، پڻ سڏيو ويندو آھي "سسٽم چونڊيندڙ" (ايپل/قطار/IOCP/etc).
پهريون طريقو شامل آهي هر ايندڙ ڪنيڪشن لاءِ هڪ نئون OS ٿريڊ ٺاهڻ. ان جو نقصان غريب scalability آهي: آپريٽنگ سسٽم ڪيترن ئي لاڳو ڪرڻو پوندو حوالن جي منتقلي и سسٽم ڪالون. اهي قيمتي آپريشن آهن ۽ ڪنيڪشن جي هڪ متاثر کن تعداد سان مفت رام جي کوٽ سبب ٿي سگهن ٿيون.
تبديل ٿيل نسخو نمايان ڪري ٿو سلسلن جو مقرر تعداد (ٿريڊ پول)، ان ڪري سسٽم کي عمل کي ختم ڪرڻ کان روڪي ٿو، پر ساڳئي وقت هڪ نئون مسئلو پيش ڪري ٿو: جيڪڏهن هڪ ٿريڊ پول في الحال بند ٿيل آهي ڊگهي پڙهڻ واري عملن جي ڪري، پوءِ ٻيا ساکٽ جيڪي اڳ ۾ ئي ڊيٽا حاصل ڪرڻ جي قابل نه هوندا. ائين ڪرڻ.
ٻيو طريقو استعمال ڪري ٿو واقعي جي نوٽيفڪيشن سسٽم (سسٽم چونڊيندڙ) OS پاران مهيا ڪيل. هي آرٽيڪل سڀ کان عام قسم جي سسٽم چونڊيندڙ تي بحث ڪري ٿو، خبردارين (واقعات، نوٽيفڪيشن) جي بنياد تي I/O عملن جي تياري بابت، بلڪه ان جي مڪمل ٿيڻ بابت اطلاع. ان جي استعمال جو هڪ آسان مثال هيٺ ڏنل بلاڪ ڊراگرام جي نمائندگي ڪري سگهجي ٿو:
انهن طريقن جي وچ ۾ فرق هن ريت آهي:
I/O عملن کي بلاڪ ڪرڻ معطل ڪرڻ استعمال ڪندڙ جي وهڪري جيستائينجيستائين OS صحيح نه آهي خراب ڪرڻ اچڻ وارو IP پيڪٽس بائيٽ وهڪرو (ٽي پي، ڊيٽا وصول ڪري رهيو آهي) يا پوءِ ذريعي موڪلڻ لاءِ اندروني لکڻ جي بفرن ۾ ڪافي جڳهه موجود نه هوندي اين آء (ڊيٽا موڪلڻ).
سسٽم چونڊيندڙ اضافي وقت پروگرام کي اطلاع ڏئي ٿو ته OS اڳ ۾ ئي defragmented IP packets (TCP، ڊيٽا استقبال) يا اندروني لکڻ جي بفرن ۾ ڪافي جاء اڳ ۾ ئي دستياب (ڊيٽا موڪلڻ).
ان کي خلاصو ڪرڻ لاءِ، هر I/O لاءِ هڪ OS ٿريڊ محفوظ ڪرڻ ڪمپيوٽر جي طاقت جو ضايع آهي، ڇاڪاڻ ته حقيقت ۾، ٿريڊ مفيد ڪم نه ڪري رهيا آهن (تنهنڪري اصطلاح "سافٽ ويئر مداخلت"). سسٽم چونڊيندڙ هن مسئلي کي حل ڪري ٿو، صارف پروگرام کي سي پي يو وسيلن کي وڌيڪ اقتصادي طور تي استعمال ڪرڻ جي اجازت ڏئي ٿو.
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 ريڪٽر جي جوڙجڪ تي مشتمل آهي فائل بيان ڪندڙ چونڊيندڙ ايپل и hash ٽيبلGHashTable، جيڪو هر ساکٽ ڏانهن نقشو ٺاهي ٿو CallbackData (هڪ واقعو سنڀاليندڙ جي جوڙجڪ ۽ ان لاءِ استعمال ڪندڙ دليل).
مهرباني ڪري نوٽ ڪريو ته اسان سنڀالڻ جي صلاحيت کي چالو ڪيو آهي نامڪمل قسم انڊيڪس جي مطابق. IN reactor.h اسان ساخت جو اعلان ڪريون ٿا reactor، ۽ ۾ reactor.c اسان ان جي وضاحت ڪريون ٿا، ان ڪري صارف کي ان جي فيلڊ کي واضح طور تي تبديل ڪرڻ کان روڪيو. هي نمونن مان هڪ آهي ڊيٽا لڪائڻ، جيڪو مختصر طور تي C semantics ۾ اچي ٿو.
ڪارڪن reactor_register, reactor_deregister и reactor_reregister سسٽم چونڊيندڙ ۽ هيش ٽيبل ۾ دلچسپي جي ساکٽ ۽ لاڳاپيل ايونٽ هينڊلر جي لسٽ کي اپڊيٽ ڪريو.
<КОД СТАТУСА> ھڪڙو انگ آھي جيڪو عمل جي نتيجي جي نمائندگي ڪري ٿو. اسان جو سرور هميشه اسٽيٽس 200 واپس ڪندو (ڪامياب آپريشن).
<ОПИСАНИЕ СТАТУСА> - اسٽيٽس ڪوڊ جي اسٽرنگ نمائندگي. اسٽيٽس ڪوڊ 200 لاءِ هي آهي OK.
<ЗАГОЛОВОК N> - ساڳئي فارميٽ جو هيڊر جيئن درخواست ۾. اسان عنوان واپس ڪنداسين Content-Length (فائل سائيز) ۽ Content-Type: text/html (واپسي ڊيٽا جو قسم).
<ДАННЫЕ> - صارف پاران درخواست ڪيل ڊيٽا. اسان جي حالت ۾، هي تصوير جو رستو آهي HTML.
فائيل http_server.c (اڪيلو موضوع وارو سرور) فائل شامل آهي common.h، جنهن ۾ هيٺين فنڪشن پروٽوٽائپ شامل آهن:
ڏيکاريو فنڪشن prototypes 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:
فعل new_server() سسٽم ڪالن پاران ٺاهيل "سرور" ساکٽ جي فائل وضاحت ڪندڙ کي واپس ڏئي ٿو socket(), bind() и listen() ۽ غير بلاڪنگ موڊ ۾ ايندڙ ڪنيڪشن قبول ڪرڻ جي قابل.
نوٽ ڪريو ته ساکٽ شروعاتي طور تي پرچم استعمال ڪندي غير بلاڪنگ موڊ ۾ ٺاھيو ويو آھي SOCK_NONBLOCKتنهنڪري فنڪشن ۾ on_accept() (وڌيڪ پڙهو) سسٽم ڪال accept() سلسلي جي عمل کي نه روڪيو.
ته reuse_port جي برابر آهي true، پوء هي فنڪشن ساکٽ کي اختيار سان ترتيب ڏيندو SO_REUSEPORT ذريعي setsockopt()ساڳئي بندرگاهن کي گھڻن موضوعن واري ماحول ۾ استعمال ڪرڻ لاءِ (ڏسو سيڪشن ”ملٽي ٿريڊ سرور“).
ايونٽ سنڀاليندڙ on_accept() سڏيو ويندو آهي OS کان پوء هڪ واقعو ٺاهي ٿو EPOLLIN، انهي صورت ۾ مطلب ته نئون ڪنيڪشن قبول ڪري سگهجي ٿو. on_accept() هڪ نئون ڪنيڪشن قبول ڪري ٿو، ان کي غير بلاڪنگ موڊ ۾ تبديل ڪري ٿو ۽ ايونٽ هينڊلر سان رجسٽر ڪري ٿو on_recv() هڪ I/O ريڪٽر ۾.
CPU لاڳاپو استعمال ڪندي، گڏ ڪرڻ سان -march=native, پي، هٽن جي تعداد ۾ اضافو ڪيش، واڌارو MAX_EVENTS ۽ استعمال ڪريو EPOLLET ڪارڪردگي ۾ هڪ اهم اضافو نه ڏنو. پر ڇا ٿيندو جيڪڏهن توهان هڪ ئي وقت جي رابطن جو تعداد وڌايو؟