Ті, хто працювали з сокетами в джаві, бачили там InputStream/OutputStream. Так так, це стріми, які відповідають за “відправку” і “отримання”. Але, як воно працює під капотом? 🤔
У нас є мережева карта і ядро операційної системи.
Драйвера забезпечують їхню взаємодію. Вони реалізують отримання і передачу даних між ядром і мережевим контролером, щоб дані, отримані мережевою картою надійшли у відкритий світ, або навпаки були отримані нашим застосунком.
Драйвер мережевої карти, зазвичай, реалізує 2 черги(в залежності від провайдера мережової карти, цих черг може бути більше) — TXQ і RXQ.
TXQ (Transmission Queue) - це черга передачі даних з мережевого контролера у світ. Коли мережевий контролер отримує дані, він зберігає їх у своєму TXQ перед тим, як відправити по мережі. TXQ допомагає зберегти порядок передачі даних та дозволяє мережевому контролеру керувати швидкістю передачі.
Аналогічно з RXQ(Receive queue), але в контексті отримання.
Черга драйвера не містить пакетних даних. Натомість вона складається з дескрипторів(“посилань”), які вказують на інші структури даних, які називаються буферами ядра сокетів (SKB), які містять пакетні дані та використовуються в усьому ядрі. SKB має бути доступний для різних процесів, які взаємодіють з сокетами. Якщо буфер зберігався в пам'яті процесу, то інші процеси не змогли б звертатися до нього без спеціального механізму міжпроцесової комунікації.
Socket kernel buffer має два окремі буфери: буфер для прийому даних та буфер для передачі даних. Ці буфери можуть містити дані для різних пакетів, які надходять або надсилаються через сокет.
Коли дані надходять до мережевої карти, вони зберігаються в RXQ черзі на рівні мережевого драйвера. Драйвер мережевої карти зберігає відомості про прийнятий пакет, включаючи адресу відправника та призначення, а також інші метадані.
Потім мережевий драйвер операційної системи отримує пакет з RXQ черги та розміщує дані у відповідному SKB буфері для прийому даних на рівні операційної системи.
Коли дані мають бути відправлені через мережу, вони записуються в SKB буфер для передачі даних програмою, що використовує сокет. Далі, операційна система зберігає ці дані у відповідному TXQ чергу на рівні мережевого драйвера. Коли мережевий драйвер мережевої карти готовий надіслати пакет, він бере дані з TXQ черги та передає їх через мережу. Таким чином, дані для відправки можуть бути записані в Socket kernel buffer буфер для передачі даних та збережені у відповідній черзі TXQ, поки вони не будуть передані через мережу.
SKB є проміжним буфером між програмою, яка використовує сокет, та мережевим контролером, який передає дані через мережу.
Тобто, записуючи/зчитуючи в/з Ouput/Input Stream, ми записуємо/зчитуємо дані у відповідний буфер в розрізі конкретного SKB.
Звідки в нього інформація, куди відправити дані?
Якщо ви чули за модель OSI, при відправці або отриманні даних, пакети інкапсулюються і декапсулюються.
Коли ми надсилаємо дані з рівня джави, він починає загортатися метаінформацією нижчих рівнів(TCP & IP), тобто наші дані доповнюються портом і IP адресом. Ця інформація і буде записана в SKB. Потім, при створенні пакету який буде поміщений в TXQ, він вже буде мати всю необіхдну інформацію, яка буде декапсульована отримувачем. І яка буде записана у відповідний SKB, бо як ми знаємо, сокет — це гніздо, яке складається з IP і порта.
PS.
Якщо зайти в клас Socket і прослідкувати як він створюється, вийдемо на клас SocketImpl, який має одне цікаве поле fd;
Дескриптор файлу є унікальним ідентифікатором, який використовується для доступу до файлів, сокетів та інших ресурсів в операційній системі. У випадку сокета, дескриптор файлу відповідає файлу, який створюється в операційній системі, коли сокет відкривається. FileDescriptor fd зберігає це посилання на файловий дескриптор сокета, який потім використовується для здійснення взаємодії з операційною системою для передачі та отримання даних.
Тобто джавівський сокет — це зручна обгортка над об’єктом сокета в операційній системі, який надає зручний механізм для запису/зчитування, грубо кажучи, у файл. Ці дані, як цибулина, згорнуті метаінформацією рівнями OSI, будуть відправлені як пакети по мережі до отримувача.
Згадавши файлові дескриптори, можна зазначити, що запис у сокет не відрізняється від запису на диск у тому сенсі, що якийсь набір даних на рівні застосунку має потрапити на диск чи мережевий контролер. У цьому процесі не можна обійтись без використання буферів.
Про них непогано описано в цій статті: How java IO works internally?
https://www.linuxjournal.com/content/queueing-linux-network-stack
https://bootlin.com/blog/multi-queue-improvements-in-linux-kernel-ethernet-mvneta/
https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch14s09.html
https://howtodoinjava.com/java/io/how-java-io-works-internally/
“Deep dive into Java NIO!” webinar from Intellias