آشنایی با سوکت های شبکه در سی و لینوکس - بخش چهارم

در دنباله سری آموزش های آشنایی با سوکت های شبکه در سی و لینوکس، در این نوشته می خواهم درباره برخی از تابع هایی گفتگو کنم که برای تبدیل نشانی های IP به کار می روند. کاربرد این تابع برای تبدیل یک نشانی IP اَسکی و قابل خوانده شدن توسط ما به ریخت بایتهای دودویی است که توسط ماشین قابل درک شدن هستند.

برای نمونه نشانی 192.168.1.100 یا نشانی - 0:0:0:0:0ffff:c0a8:164 برای ما قابل خواندن و درک شدن است ولی ماشین که می خواهد به کمک لایه های TCP/IP بسته شبکه (Network Packet) را آماده کند و در پایان بر روی اینترنت قرار داده و به به ماشین دیگر بفرستند، نیاز دارد تا بایت های دودویی صفر و یک را داشته باشد.

https://virgool.io/linux-internals/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D8%B3%D9%88%DA%A9%D8%AA-%D9%87%D8%A7%DB%8C-%D8%B4%D8%A8%DA%A9%D9%87-%D8%AF%D8%B1-%D8%B3%DB%8C-%D9%88-%D9%84%DB%8C%D9%86%D9%88%DA%A9%D8%B3-%D8%A8%D8%AE%D8%B4-%DB%8C%DA%A9%D9%85-wbohvfgswvmm
https://virgool.io/linux-internals/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D8%B3%D9%88%DA%A9%D8%AA-%D9%87%D8%A7%DB%8C-%D8%B4%D8%A8%DA%A9%D9%87-%D8%AF%D8%B1-%D8%B3%DB%8C-%D9%88-%D9%84%DB%8C%D9%86%D9%88%DA%A9%D8%B3-%D8%A8%D8%AE%D8%B4-%D8%AF%D9%88%D9%85-e4ku3bqi7tsj
https://virgool.io/linux-internals/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D8%B3%D9%88%DA%A9%D8%AA-%D9%87%D8%A7%DB%8C-%D8%B4%D8%A8%DA%A9%D9%87-%D8%AF%D8%B1-%D8%B3%DB%8C-%D9%88-%D9%84%DB%8C%D9%86%D9%88%DA%A9%D8%B3-%D8%A8%D8%AE%D8%B4-%D8%B3%D9%88%D9%85-lb8d7ce5sqig

ترتیب بایت شبکه

در رایانه ها (چه دسکتاپ، سرور،موبایل یا هر ابزار دیگری) دو شیوه Little Endian و Big Endian برای ذخیره سازی بایت ها درون حافظه و دیسک ها هست. برای نمونه در نگهداری 56a7 نخست بایت های 56 و سپس بایت های a7 نگهداری می شوند که این شیوه Big Endian است.

ولی بیشتر سیستم های عامل مانند لینوکس، ویندوز و مکینتاش (و شاید همه BSD ها) شیوه Little Endian را به کار می برند به گونه ای که نخست بایت های a7 و سپس 56 نگهداری می شوند. سیستم عامل سولاریس شیوه Big Endian را به کار برده است.

https://virgool.io/@linux_internals/%D9%85%D9%81%D8%A7%D9%87%DB%8C%D9%85-little-endian-%D9%88-big-endian-rt5c3ailish0

بنابراین رایانه ها یک شیوه یکپارچه و همگانی برای نگهداری بایت ها را به کار نمی برند و از این برای آنکه رایانه ها و سیستم عامل های گوناگون بتوانند بر روی اینترنت و شبکه با یکدیگر در هم کنشی (تعامل) باشند، پس پروتکل های اینترنتی باید یک رویکرد یکپارچه و همگانی برای ترتیب بایت ها را به کار ببرند، زیرا قاعدتا نبودن این شیوه یکپارچه، باعث می شود در سوی دیگر، داده نادرست تفسیر شود. این شیوه یکپارچه برای ترتیب بایت های فرستاده شده روی شبکه را ترتیب بایت شبکه (Network Byte Order) می گویند.

بنابراین زمانی که می خواهیم یک سوکت شبکه را بسازیم، می بایست که اطمینان داشته باشیم که داده های درون عضوهای sin_addr و sin_port از ساختار struct sockaddr_in برای IPv4 و struct coakaddr_in6 برای IPv6 ترتیب بایت شبکه را داشته باشند. برای براوردن این، دو دسته از تابع ها فراهم شده است.

  • تابع هایی که نشانی هاست (IP) را به ترتیب بایت شبکه تبدیل می کنند که دو تابع ()htons و ()htonl در این دسته هستند.
  • تابع هایی که ترتیب بایت شبکه را نشانی هاست (IP) تبدیل می کنند که دو تابع ()ntphs و ()ntohl در این دسته هستند.
Network Byte Order
Network Byte Order

بنابراین دو دسته تابع با نام هایی به ریخت H-to-N که برای تبدیل نشانی های IP به جریان بایت های دودویی شبکه و وارونه آن، N-to-H هستند که جریان بایت های دودویی شبکه را به نشانی های IP قابل خوانده شده تبدیل می کنند.

این چهار تابع به گونه ماکروها در فایل سرآیند arpa/inet.h شناسانده شده اند و از این رو برای به کارگیری آنها در فایل سی باید سرآیند arpa/inet.h را include# کنیم.

همچنین این تابع همگی به دو دسته short (پایان نام s) و long (پایان نام l) دسته بندی می شوند. unsigned short گونه ۱۶ بیتی (۲ بایتی) و unsigned long گونه ۳۲ بیتی (۴ بایتی) است. در کد سی زیر می توانیم بررسی کنیم سیستم عاملی که کد درون آن انجام شده Big Endian یا Little Endian است.

نمونه کد سی برای تشخیص سیستم Endian
نمونه کد سی برای تشخیص سیستم Endian

نخست در خط های ۳ و ۴ دو ماکر به نام های BIG_ENDIAN و LITTLE_ENDIAN با مقدارهای 0 و 1 نوشته شده است. سپس یک تابع به نام ()TestByteOrder که مقداری int را برگشت می دهد را نوشته ایم. درون تابع نخست یک متغیر short int به نام word با مقدار 0x0001 شناسانده ایم و در دنباله این مقدار int را به رشته از کاراکترها تبدیل (Cast) کرده ایم که برآیند در متغیری به نام b نگهداری می شود.

درون عبارت پیش روی return که همانند if..else است، بررسی می کنیم که آیا عنصر نخست درون رشته b برابر با شماره ای است که در ماکرو BIG_ENDIAN نگهداری می شود یا وگرنه برابر با مقداری است که در ماکرو LITTLE_ENDIAN نگهداری می شود.

اگر سیستم عامل از Big Endian پشتیبانی می کند پس بایت 00 شماره 0x0001 باید نخست نگهداری شود که برابر با مقدار 0 است که در ماکرو BIG_ENDIAN نگهداری می شود وگرنه اگر از LITTLE_ENDIAN پشتیبانی کند، پس بایت 01 نخست نگهداری می شود که برابر با مقدار 1 است که در ماکرو LITTLE_ENDIAN نگهداری می شود.

Linux Support Little Endian
Linux Support Little Endian

کد زیر نمونه ای ساخت سوکت با ()socket و چگونگی مقداردهی درست عضوهای ساختار struct sockaddr_in را نشان می دهد. نخست دو متغیر int به نام port و ip با مقدارهای 9090 و INADDR_ANY را نوشته ایم. INADDR_ANY یک ماکرو است که در فایل سرآیند netinet/in.h شناسانده شده است و به نشانی 0.0.0.0 اشاره دارد.

در IPv4 نشانی های ویژه ای هستند، مانند 127.0.0.1 که نشانی Loop Back یا نشانی به ماشین محلی یا لوکال هاست است. نشانی دیگر، 0.0.0.0 است که در واقع زمانی که بخواهیم به همه کارت های شبکه اشاره کنیم، می توانیم 0.0.0.0 را به کار ببریم.

گاهی بر روی سرور چندین اینترفیس شبکه است و برای اینکه سوکت را به گونه ای بسازیم که بتواند بر روی شمار پورت و بر روی همه این آدرس ها گوش دهد، پس می توانیم جای تک تک آدرس های IP، نشانی 0.0.0.0 را به کار ببریم که در زبان سی ماکرو INADDR_ANY که int هست به این نشانی اشاره می کند.

سپس یک سوکت UDP ساخته ایم زیرا که دومین ورودی SOCK_DRAM است و فامیلی آدرس آن IPv4 است، زیرا که ورودی یکم آن AF_INET است. سپس یک نمونه از ساختار sockaddr_in ساخته ایم و همانگونه که پیش از این گفته شد، باید با متدهای H-to-N شماره پورت و نشانی IP را ابه بایت شبکه تبدیل کنیم.

htons() - htonl()
htons() - htonl()

شماره های پورت ۱۶ بیتی یا ۲ بایتی و دربازه شماره صفر تا ۶۵۵۳۵ هستند، پس ()htons را به کار برده ایم و چون شمارهای IPv4 برابر با ۳۲ بیت یا ۴ بایت هستند، پس ()htonl را به کار برده ایم. آنچه که در کد بالا گفته شد، یک نمایش از چگونگی ساخت سوکت است که با فراخوانی متد ()socket و سپس ساخت یک نمونه از ساختار struct sockaddr_in برای IPv4 انجام می شود.

در کد بالا فرض بر نوشتن سوکت سرور است ولی همه چیز را نشان نمی دهد. در دنباله کد باید آدرس سوکتی IPv4 که در برگیرنده شماره پورت server_socket.sin_port و نشانی IP یعنی server_socket.sin_addr.s_addr را باید پس از این به سوکتی که فایل توصیفگر آن را ساخته ایم انقیاد (Bind) کنیم که این کار توسط تابع ()bind انجام می شود. در واقع با ()bind است که می گویم سوکت ساخته و نگهداری شده در (اینجا) متغیر server_socket_fd بر روی کدام نشانی (ها) و شماره پورت گوش دهد.

شاد و پیروز و تندرست باشید.