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

سوکت مفهومی پایه ای در شبکه ها است که ارتباط میان دو فرایند در دو سوی شبکه (کلاینت - سرور) را فراهم می کند. به طور کلی نیاز است تا دو فرایند یا دو مولفه از فضای کاربر و فضای هسته با یکدیگر ارتباط داشته باشند. این دو فرایند می توانند بر روی یک رایانه و یا بر روی دو رایانه جدا از هم باشند ولی این ارتباط می تواند از راه یک سوکت انجام شود.

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

در مفاهیم سیستم عامل Inter-Process Communication یا IPC به ارتباط میان دو فرایند اشاره دارد. در سیستم عامل های گوناگون شیوه های گوناگونی برای ارتباط میان دو فرآیند هست که برخی میان سیستم عامل ها همسان و برخی دیگر ویژه همان سیستم عامل هستند. برای نمونه فهرست زیر مکانیزم های شناخته شده و مشهور برای ارتباط میان فرایندها را نشان می دهد:

  • سوکت ها
  • فایل های اشتراکی
  • حافظه اشتراکی
  • فایل های Pipe دارای نام و بی نام
  • سیگنال ها
  • فرستادن پیغام ها - صفی از پیغام ها

سوکت شبکه چیست

برای ارتباط میان دو سوی یک شبکه که به ریخت کلاینت (سرویس گیرنده) و سرور (سرویس دهنده) است، از سوکت شبکه کمک گرفته می شود. سوکت های شبکه بر پایه دو مولفه هستند: ۱) نشانی IP یا نام دامنه (FQDN) و ۲) شماره درگاه (پورت)

هر سرویس دهنده برای نمونه پروتکل های HTTP, DHCP, DNS و یا برنامه های شبکه مانند Telegram, Yahoo Messanger, Skype و یا هر برنامه تحت شبکه ای دیگری دارای یک یا چندین آدرس و یک شماره درگاه هست.

شماره درگاه به هر رو، یک شماره یکتا برای آن برنامه است. برای نمونه برای پروتکل HTTP شماره پیش فرض و رِزِرو شده ۸۰ و برای HTTPS شماره ۴۴۳ است ولی می توان شماره درگاه را برای این دو تغییر داد ولی پروتکل دیگری نمی تواند این دو شماره را برای خود ثبت کند.

بر فرض اینکه می خواهیم یک برنامه ارتباط میان کارمندان درون شرکت و بر روی شبکه LAN بنویسیم، دو راهکار داریم. نخست آن که اگر برنامه بر پایه پروتکل HTTP باشد، پس شماره درگاه را می توانیم همان ۸۰ یا هر شماره دیگری مانند ۸۰۸۰ یا ۸۰۰۰ یا ۵۰۰۰ یا ۵۰۰۱ به کار ببریم. دوم آن که می توانیم شماره درگاهی دیگر را برگزینیم. به هر رو سوکت دارای دو مولفه زیر است.

socket = (IP Aaddress, Port Number)

که در آن IP می تواند IPv4 یا IPv6 باشد. در برقراری این ارتباط، کلاینت آغاز کننده ارتباط است و برای برقراری این اربتباط، نیاز به نشانی IP و شماره درگاهی داریم که در سوی دیگر سرور بر روی این دوتایی (IP, Port) به درخواست ها گوش می دهد.

ساخت سوکت

برای برقراری ارتباط میان دوی سوی شبکه، نیاز است تا هم در سوی کلاینت و هم در سوی سرور یک سوکت ساخته شود. سوکت ها پلی میان ماشین کلاینت و سرور هستند و آنها را می توان نقطه پایانی (End Point) میان دو سوی شبکه دانست.

از دید سوکت های بِرِکلی، تابع هایی که در ادامه فهرست شده اند برای ساخت و به کارگیری سوکت های شبکه در سی استاندارد و همچنین در دیگر زبان های برنامه نوسی مانند سی پلاس پلاس، پایتون، جاوا و دیگر زبان ها به کار می روند.

  • تابع socket برای ساخت و آماده سازی آغازین یک سوکت. این تابع در برنامه نویسی سوی کلاینت و سرور به کار می رود. زیرا در کنار اینکه باید در سرور ورودی برای دریافت درخواست ها باشد، باید در سوی کلاینت نیز ورودی باشد که پاسخ ها را دریافت کند.
int sockfd = socket(domain, type, protocol)

به طور کلی سوکت یک در (نقطه پایانی) میان سرور و کلاینت است. پلاک این در یک دوتایی است که در سوی کلاینت CLIENT_IP_ADDR:RANDOM_PORT و در سوی سرور SERVER_IP_ADDR:FIXED_PORT است. در سوی کلاینت خود سیستم عامل یک شماره پور تصادفی مانند ۵۶۰۲۵ را به سوکت کلاینت اختصاص می دهد ولی در سوی سرور باید آشکارا یک سوکت را مشخص کنید تا کلاینت از راه آن به سرور وصل شود.

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

توجه داشته باشید اطلاعات کلاینت IP و PORT در غالب بسته (Packet) به سرور فرستاده می شود، پس سرور با باز کردن این بسته در لایه مربوطه در TCP/IP می فهمد که پاسخ را به چه کسی بفرستد. همچنین در بسته اطلاعات سرور نیز مشخص شده است، پس روترها می فهمند که بسته باید به کدام گیرنده فرستاده شوند.

  • تابع sockoption برای شناساندن گزینه های گوناگون یک سوکت
int setsockopt(int sockfd, int level, int optname,  const void *optval, socklen_t optlen);
  • تابع bind برای انقیاد (Binding) دوتایی نشانی IP و شماره درگاه به یک سوکت. به یاد داشته باشید این تابع تنها درون برنامه نویسی سوی سرور به کار می رود.
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • تابع connect برای اتصال کلاینت به سرور به کار می رود. این تابع تنها در برنامه نویسی کلاینت به کارمی رود. قاعدتا کلاینت باید به سرور وصل شود تا درخواست بفرستد.
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • تابع listen برای این است که در سوی سرور، سرور بتواند به درخواست های ورودی گوش دهد. این تابع نیز تنها در برنامه نویسی سرور به کار می رود.
int listen(int sockfd, int backlog);
  • تابع accept در سوی سرور برای تایید یک درخواست ورودی به کار می رود.
int new_socket= accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • تابع های send و recv که برای فرستادن و دریافت داده ها میان کلاینت و سرور به کار می روند. هر دو تابع در برنامه نویسی سرور و کلاینت می توانند به کار روند.
int send(SOCKET socket, const char * buffer, int buflen, int flags);
int recv(SOCKET socket, char * buffer, int buflen, int flags);
  • تابع close در پایان کار یک سوکت را قطع و اصطلاحا آن را می بندد.

شکل زیر دید کلی از چگونگی ساخت و رفتار سوکت های شبکه را نشان می دهد. سرآغاز کار با آماده سازی آغازین (Initial) یک سوکت است که به کمک ()socket در هر دو سو انجام می شود. سپس باید در سوی سرور دوتایی های نشانی IP و شماره درگاه به سوکت انقیاد (Bind) شوند که به کمک ()bind انجام می شود. در دنباله در سوی سرو باید ()listen فراخوانی شود تا سرور آماده گوش دادن به درخواست ها شود. در سوی دیگر، یعنی کلاینت نیز یک سوکت با ()socket ساخته شده و اکنون می تواند به کمک ()connect به سرور وصل شود.

اکنون که کلاینت به سرور وصل شده، پس درخواست ورودی باید به کمک ()accept تایید شود و در این صورت، از این پس می تواند داده یا اطلاعات را رد و بدل کرد که با ()send و ()recv انجام می شود. در پایان، برای بستن و آزاد سازی سوکت، باید در هر دو سو ()close فراخوانی شود.

چرا باید سوکت در پایان آزاد شود

در سیستم عامل های یونیکسی مانند BSD و مکینتاش و شبه یونیکسی مانند لینوکس و سولاریس، همه چیز به ریخت یک فایل است. این فایل می تواند یک فایل متنی ساده، فایل های دودویی مانند فایل های عکس، ویدیو، فایل هایی برای دیوایس ای سخت افزاری که در برگیرنده دو دسته بلاک دیوایس ها مانند هارد دیسک و کاراکتر دیوایس مانند ماوس و کی بورد، فایل های Pipe و همچنین فایل هایی برای سوکت ها می شود.

در مفاهیم سیستم عامل، توصیفگر فایل (File Descriptor) شماره ای صحیح و نا منفی است که به یک فایل باز شده اشاره می کند. برای نمونه در هر زبانی مانند سی یا پایتون یا جاوا، در لینوکس فایلی باز شود، برآیند برگشتی یک FD خواهد بود.

بنابراین سیستم عامل به هر فایل باز شده و همچنان باز، یک شماره FD اختصاص می دهد که این درباره سوکت ها نیز صدق می کند. از این رو، تابع ()socket اگر با کامیابی انجام شود، برآیند آن یک شماره int است که همان FD مربوط به سوکت است.

int socket_fd;

socket_fd = socket(domain, type, protocol)

و از همین رو است که باید نام متغیری که به برآیند ()socket اشاره می کند را به تابع ()close بفرستیم تا سوکت آزاد یا قطع شود. همچنین متدهای دیگر، sockopt و bind و listen و connect نیز، ورودی یکم آنها، یک توصیفگر فایل است که به یک سوکت اکنون باز اشاره می کند.

آنچه که گفته شد پیشگفتاری بر سوکت های بِرِکلی در سی و لینوکس است که برای پیاده سازی یک برنمه سرور-کلاینت ساده، باید یک سری دیگر از ریزه کاری ها در نوشته هایی دیگر گفته شود. بنابراین دنباله نوشته برای آینده.

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